> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# Declare product stack

PUT https://api.runtype.com/v1/products/{id}/stack/declared
Content-Type: application/json

Replace what one source declares the product is built on (frameworks, vector databases, upstream services, versions). The Overview shows declared items beside the ones detected from the product's own configuration. The actor is the caller's credential: a named API key reads as its name, anyone else by first name.

Reference: https://docs.runtype.com/api-reference/runtype-api/products/declare-product-stack

## Authentication

- `Authorization` header (bearer token, required) — API key or Clerk session token

## Servers

- `https://api.runtype.com` (Production, default)
- `https://api.runtype-staging.com` (Staging)

## Request

### Path parameters

- `id` (string, required)

### Body (application/json)

This endpoint expects a DeclareProductStackRequest.

- `items` (list of ProductStackItem, required) — Everything this source says the product is built on. Replaces the previous declaration from the same source whole; an empty list clears it. Keys follow the detected ones (model:\<id>, data:\<engine>, runtime:\<name>, integration:\<id>) so a declared item and a detected one show once, else custom:\<slug>.
- `source` (enum, required) — Where the declaration comes from: the Runtype MCP server, the CLI, or the API
  - Allowed values: `mcp`, `cli`, `api`

## Response

### 200

Declaration stored

- `declaration` (ProductDeclaredStack, required)
- `productId` (string, required)
- `source` (enum, required)
  - Allowed values: `mcp`, `cli`, `api`
- `stack` (ProductStack, required) — The Overview's stack. The legacy techStack buckets are separate and unchanged.

## Errors

### 400 Bad Request Error

Validation error

- `error` (string, required)
- `details` (list of ErrorDetailsItems, optional)

### 401 Unauthorized Error

Unauthorized

- `error` (string, required)
- `details` (list of ErrorDetailsItems, optional)

### 403 Forbidden Error

Insufficient permissions

- `error` (string, required)
- `details` (list of ErrorDetailsItems, optional)

### 404 Not Found Error

Product not found

- `error` (string, required)
- `details` (list of ErrorDetailsItems, optional)

### 500 Internal Server Error

Internal server error

- `error` (string, required)
- `details` (list of ErrorDetailsItems, optional)

## Types

### ProductStackItem

- `key` (string, required) — Stable chip key, e.g. model:claude-sonnet-5 or custom:inngest
- `label` (string, required)
- `category` (string, optional)
- `mark` (string, optional) — Brand or provider mark id the dashboard draws
- `url` (string, optional)
- `version` (string, optional)
- `where` (string, optional) — Where a detected item was found

### ProductDeclaredStack

- `actorLabel` (string, required)
- `declaredAt` (string, required)
- `items` (list of ProductStackItem, required)
- `actorUserId` (string, optional)

### ProductStack

The Overview's stack. The legacy techStack buckets are separate and unchanged.

- `declared` (ProductStackDeclared, optional) — The last declaration from each source; a product save keeps it as stored
- `detected` (ProductStackDetected, optional) — What the last detection derived; a product save keeps it as stored
- `hidden` (list of string, optional) — Keys of detected or declared chips people hid; replaces the stored list
- `hiddenAt` (map from string to string, optional) — When each hidden key was hidden; set by the server
- `manual` (list of ProductStackItem, optional) — Chips people added by hand; replaces the stored list

### ErrorDetailsItems

- `message` (string, required)
- `code` (string, optional) — Stable Runtype validation code (e.g. MISSING_REQUIRED_FIELD, INVALID_ENUM).
- `path` (string, optional) — Dotted JSON path to the offending field.
- `suggestion` (string, optional) — Actionable hint (allowed values, accepted range, …).

### ProductStackDeclared

The last declaration from each source; a product save keeps it as stored

- `api` (ProductDeclaredStack, optional)
- `cli` (ProductDeclaredStack, optional)
- `mcp` (ProductDeclaredStack, optional)

### ProductStackDetected

What the last detection derived; a product save keeps it as stored

- `derivedAt` (string, required)
- `items` (list of ProductStackItem, required)

## Examples

**Request**

```json
{
  "items": [
    {
      "key": "string",
      "label": "string"
    }
  ],
  "source": "mcp"
}
```

**Response**

```json
{
  "declaration": {
    "actorLabel": "string",
    "declaredAt": "string",
    "items": [
      {
        "key": "string",
        "label": "string",
        "category": "string",
        "mark": "string",
        "url": "string",
        "version": "string",
        "where": "string"
      }
    ],
    "actorUserId": "string"
  },
  "productId": "string",
  "source": "mcp",
  "stack": {
    "declared": {
      "api": {
        "actorLabel": "string",
        "declaredAt": "string",
        "items": [
          {
            "key": "string",
            "label": "string",
            "category": "string",
            "mark": "string",
            "url": "string",
            "version": "string",
            "where": "string"
          }
        ],
        "actorUserId": "string"
      },
      "cli": {
        "actorLabel": "string",
        "declaredAt": "string",
        "items": [
          {
            "key": "string",
            "label": "string",
            "category": "string",
            "mark": "string",
            "url": "string",
            "version": "string",
            "where": "string"
          }
        ],
        "actorUserId": "string"
      },
      "mcp": {
        "actorLabel": "string",
        "declaredAt": "string",
        "items": [
          {
            "key": "string",
            "label": "string",
            "category": "string",
            "mark": "string",
            "url": "string",
            "version": "string",
            "where": "string"
          }
        ],
        "actorUserId": "string"
      }
    },
    "detected": {
      "derivedAt": "string",
      "items": [
        {
          "key": "string",
          "label": "string",
          "category": "string",
          "mark": "string",
          "url": "string",
          "version": "string",
          "where": "string"
        }
      ]
    },
    "hidden": [
      "string"
    ],
    "hiddenAt": {},
    "manual": [
      {
        "key": "string",
        "label": "string",
        "category": "string",
        "mark": "string",
        "url": "string",
        "version": "string",
        "where": "string"
      }
    ]
  }
}
```

**SDK Code**

```python
import requests

url = "https://api.runtype.com/v1/products/id/stack/declared"

payload = {
    "items": [
        {
            "key": "string",
            "label": "string"
        }
    ],
    "source": "mcp"
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.put(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.runtype.com/v1/products/id/stack/declared';
const options = {
  method: 'PUT',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"items":[{"key":"string","label":"string"}],"source":"mcp"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.runtype.com/v1/products/id/stack/declared"

	payload := strings.NewReader("{\n  \"items\": [\n    {\n      \"key\": \"string\",\n      \"label\": \"string\"\n    }\n  ],\n  \"source\": \"mcp\"\n}")

	req, _ := http.NewRequest("PUT", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.runtype.com/v1/products/id/stack/declared")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Put.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"items\": [\n    {\n      \"key\": \"string\",\n      \"label\": \"string\"\n    }\n  ],\n  \"source\": \"mcp\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.put("https://api.runtype.com/v1/products/id/stack/declared")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"items\": [\n    {\n      \"key\": \"string\",\n      \"label\": \"string\"\n    }\n  ],\n  \"source\": \"mcp\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('PUT', 'https://api.runtype.com/v1/products/id/stack/declared', [
  'body' => '{
  "items": [
    {
      "key": "string",
      "label": "string"
    }
  ],
  "source": "mcp"
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.runtype.com/v1/products/id/stack/declared");
var request = new RestRequest(Method.PUT);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"items\": [\n    {\n      \"key\": \"string\",\n      \"label\": \"string\"\n    }\n  ],\n  \"source\": \"mcp\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "items": [
    [
      "key": "string",
      "label": "string"
    ]
  ],
  "source": "mcp"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.runtype.com/v1/products/id/stack/declared")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "PUT"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```