---
title: "Idempotent bulk upsert keyed on externalId (external connector intake)"
method: POST
path: "/api/contexts/bulk-upsert"
tags: ["Contexts"]
---

# Idempotent bulk upsert keyed on externalId (external connector intake)

`POST /api/contexts/bulk-upsert`

## Request

- Base URL: `http://localhost:38200`
- URL: `http://localhost:38200/api/contexts/bulk-upsert`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `token`

Used by external connectors (e.g. Trailcrumb) to push endpoints/trails. Matches each item on (tenant, externalId): existing rows are updated, new ones created. `title`/`body` map to `name`/`content` server-side.

## Request body

- object
  - `workspace` string: Falls back to X-Workspace header / active scope / GLOBAL when omitted.
  - `contexts` object[], required
    - `externalId` string, required
    - `type` 'reference' | 'feedback' | 'project' | 'incident' | 'lesson' | 'user' | 'synthesis' | 'map' | 'moc' | 'handoff' | 'endpoint' | 'trail', required
    - `title` string, required
    - `body` string, required
    - `description` string
    - `metadata` object
    - `tags` string[]

## Response `200`

Upsert summary.

- object
  - `upserted` integer
  - `created` integer
  - `updated` integer
  - `denied` object[]: Items a tenant hook rule refused (T919); they were not written.
    - `externalId` string
    - `rule` string
    - `reason` string

## Other responses

- `400`: Request body/query failed Zod validation.
- `401`: Missing or invalid credentials.
- `402`: Plan quota exceeded.

---

[API](https://skmtc.dev/contextq/apis/contextq-api.md) · [All operations](https://skmtc.dev/contextq/apis/contextq-api/llms.txt) · [OpenAPI document](https://skmtc.dev/contextq/apis/contextq-api/revisions/7afc44056a22?raw)
