---
title: "Import Catalog Skills"
method: POST
path: "/skills/catalog/import"
tags: ["SKILLS"]
---

# Import Catalog Skills

`POST /skills/catalog/import`

Copy catalog skills into the caller's library and attach them.

An import is a copy, not a link: the bundle is written to the caller's own
prefix, so the result is an ordinary skill that can be edited, renamed and
deleted. ``source_ref`` records which upstream revision it came from.

Ordered in three phases, and the boundaries between them are the point:

1. **Resolve and validate**, touching neither S3 nor a transaction. A
   rejected selection therefore leaves nothing behind at all.
2. **Upload every bundle**, still outside any transaction. Uploads used to
   run inside it, which meant every workspace lock and every inserted row
   was held across up to 50 sequential S3 round trips — the transaction was
   open for as long as the slowest storage call took.
3. **Insert and attach**, in one short transaction.

Rows are inserted in ascending name order so two concurrent imports by the
same user take the unique index's locks in the same sequence. Interleaved
orders deadlock, and a deadlock surfaces as ``OperationalError`` rather than
``IntegrityError`` — so it would have escaped the 409 handler that exists for
exactly that collision and become a 500.

## Request body

- ImportCatalogSkillsRequest — Import one or more catalog skills and attach them to workspaces. The whole selection arrives in one request: importing seven dbt skills as seven calls leaves a window where some are attached and some are not, and the workspace choice applies to the selection as a whole.
  - `items` ImportCatalogSkill[], required
    - `catalog_id` string, required
    - `name` string, nullable
    - `description` string, nullable
  - `privacy` 'public' | 'private'
  - `datamate_ids` integer[]

## Response `201`

Successful Response

- CustomSkillListResponse — A fixed set of skills the caller just created. Not the browse list — that is paginated (``Page[CustomSkillSummary]``). This is the result of one import request, so its size is the caller's selection and pagination would be noise.
  - `skills` CustomSkillSummary[], required
    - `public_id` string, required
    - `name` string, required
    - `description` string, nullable
    - `privacy` 'public' | 'private'
    - `file_count` integer
    - `source_ref` SkillSourceRef — Where an imported skill was copied from. Present only on skills imported from the public catalog; ``None`` means the skill was uploaded or written by hand. Carried on the list payload so the Skills table can attribute a row to its upstream origin without a second request, and so a renamed copy can still be traced back.
      - `repo` string, required
      - `path` string, required
      - `commit_sha` string, required
      - `imported_at` string, nullable
    - `created_by` integer, required
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required

## Other responses

- `400` — Invalid skill bundle
- `404` — Catalog entry or workspace
- `409` — Skill name already in use
- `422` — Validation Error
- `503` — Catalog unreachable

## Changes

- **2026-09-02** `fcb62c79da95` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/myaltimate/apis/fastapi/changes/skills/catalog/import/post.md)

---

[API](https://skmtc.dev/myaltimate/apis/fastapi.md) · [All operations](https://skmtc.dev/myaltimate/apis/fastapi/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/myaltimate/fastapi/revisions/b8f442483dc9/schema)
