Manage enterprise collections, Plugins, Skills, Connectors, categories, and distribution policies.
Use the Enterprise Marketplace APIs to publish your team’s Plugins, Skills, and Connectors to organization collections and manage visibility, categories, and distribution.
Read Get API Key and Conventions before calling these APIs.
A collection’s
Unavailable timestamps can be empty strings. ConnectorConfig accepts only the listed connection fields. Protocols, URLs, and runtime kinds must satisfy the product’s connection rules; OAuth credentials and authentication configurations are not accepted.
No response body.
file and metadata are multipart fields. All remaining fields belong inside the metadata JSON, not in separate parts.
file and metadata are multipart fields. All remaining fields belong inside the metadata JSON, not in separate parts.
No response body.
No response body.
Each request accepts one ZIP containing one standalone Plugin or Skill. A Plugin can contain multiple nested Skills. The file limit is 50 MiB; the full multipart request limit is 51 MiB. Uncompressed ZIP contents are limited to 256 MiB and 10,000 entries. Path traversal, symbolic links, corrupted packages, and batches of independent extensions are rejected. Use the management endpoints to change categories or distribution separately from a package update.
A standalone Skill must contain exactly one top-level directory. Its directory name becomes the Skill name, and it must contain a case-sensitive
Run the following command from the parent directory of
A Plugin must contain exactly one
The
Run the following command from the directory containing
Errors follow the OpenAPI conventions. Keep requestId for troubleshooting. Common HTTP statuses are listed below; use the actual code from the response.
For marketplace imports, name conflicts return 409/AlreadyExists; a missing source returns 404/NotFound; a type mismatch, delisted item, or parsing failure returns 400/BadRequest; download/upload dependency failures return 503/DEPENDENCY_UNAVAILABLE. Overwrite imports do not guarantee atomic rollback: query the collection after a failure.
Your organization must have the enterprise marketplace capability enabled. Use an API Key belonging to that organization.
Requirements
- Base URL:
https://api.qoder.com. - Common path prefix:
/v1/organizations/{organization_id}/apphub. - Send
Authorization: Bearer <api_key>. The key must belong toorganization_id. - Use
Content-Type: application/jsonfor JSON requests. For uploads, usemultipart/form-dataand let your HTTP client generate the boundary. - Example UUIDs are placeholders. Replace them with actual resource IDs. Organizations, keys, and content are isolated by region.
| Path parameter | Type | Description |
|---|---|---|
organization_id | string | Required for every endpoint; organization ID. |
collection_id | string | Required when present in the path; id returned by collection APIs. |
extension_id | string | Required when present in the path; id returned by extension APIs. |
category_id | string | Required when present in the path; id returned by category APIs. |
Visibility and distribution
A collection’s visibility controls whether members can see it in the marketplace. An extension’s distribution controls which members receive it and how it is enabled. Forced and silent enablement apply to extensions, not collections.
Use the Group APIs to obtain group id values for targetGroupIds. These must be groups in the same organization, not member IDs or billing group IDs.
Omitting distribution when uploading or registering defaults to scope=none and clientPolicy=configurable; it does not distribute to everyone. Package updates preserve the existing policy. When changing scope to none, omitting clientPolicy preserves the current client policy. Clients receive committed changes through their normal synchronization mechanism.
Pagination and request conventions
- Lists use
maxResults(default 20, range 1–100) andnextToken. The last page explicitly returnsnextToken: ""; empty lists use[]. - Keep the organization, path, filters, and page size unchanged on subsequent pages, replacing only
nextToken. Do not parse or construct tokens. Pagination does not guarantee snapshot consistency. - Queries and updates return 200; creates, uploads, and imports return 201; deletes return 204 with no body.
- Unknown fields, duplicate JSON keys, and multiple JSON values are rejected. JSON metadata is limited to 64 KiB. Text limits are measured in UTF-8 bytes.
- Before updating Connector metadata or distribution, retrieve its details and pass
revisionasexpectedRevision. Conflicts return 409. Plugin and Skill requests must not include this field. - After a lost response or timeout, query the resource before retrying, especially for generated versions and overwrite imports.
Data models
Collection
| Field | Type | Description |
|---|---|---|
id | string | Collection ID. |
organizationId | string | Organization ID. |
name | string | Collection name. |
displayName | string | Display name. |
description | string | Collection description. |
type | string | plugin, skill, or connector; immutable. |
products | string[] | Target products, e.g. ["qoder"]. |
visibility | Visibility | Marketplace visibility. |
createdAt / updatedAt | string | Creation/update timestamps in RFC3339 format. |
Visibility
| Field | Type | Description |
|---|---|---|
scope | string | none: hidden from everyone; all: visible to everyone; group: visible to selected groups. |
targetGroupIds | string[] | Required and non-empty only for group; up to 1,000 unique group IDs from this organization. Omit or use [] for other scopes. |
Distribution
| Field | Type | Description |
|---|---|---|
scope | string | none: no distribution; all: all members; group: selected groups. |
targetGroupIds | string[] | Required and non-empty only for group; up to 1,000 unique group IDs from this organization. Omit or use [] for other scopes. |
clientPolicy | string / null | configurable: users can enable or disable; force_enabled: visible and forcibly enabled; silent_enabled: silently enabled. Required in requests unless scope is none. Responses can contain null for legacy combinations that do not map to these policies. |
Extension
| Field | Type | Description |
|---|---|---|
id / organizationId / collectionId | string | Extension, organization, and collection IDs. |
type | string | plugin, skill, or connector. |
name / displayName / description | string | Name, display name, and description. |
version | string | Content version. |
categoryId | string / null | Category ID; null when uncategorized. |
clients | string[] | Target products inherited from the collection. |
status | string | Extension status, e.g. active or archived. |
distribution | Distribution | Distribution scope and client policy. |
fileHash | string | Package file hash; can be empty when no package exists. |
fileSize | integer | Package size in bytes. |
config | ConnectorConfig | Public connection configuration; omitted when unavailable. |
revision | integer | Returned only for Connectors; send as expectedRevision when updating. |
createdAt / updatedAt | string | RFC3339 timestamps. |
Category
| Field | Type | Description |
|---|---|---|
id / organizationId | string | Category and organization IDs. |
name | string | Category name. |
sortOrder | integer | Sort order. |
usageCount | integer | Number of extensions referencing the category. |
isSystem | boolean | Whether this is a system category. uncategorized cannot be modified or deleted. |
createdAt / updatedAt | string | RFC3339 timestamps. |
Changelog
| Field | Type | Description |
|---|---|---|
id | string | Changelog ID. |
action | string | Action, e.g. created, updated, scope_changed, status_changed, archived. |
version / scope / status | string | Version, distribution scope, and status recorded for the change. |
changeNote | string | Change note. |
operator.type | string | user or apiKey. |
operator.id | string | User ID or organization API Key ID, never the API Key secret. |
operator.email | string | User email; omitted when unavailable. |
createdAt | string | RFC3339 timestamp. |
MarketplaceCandidate
| Field | Type | Description |
|---|---|---|
sourceType | string | qoder_plugin or qoder_skill. |
sourceId | string | Public marketplace item ID for the import request. |
name / displayName / description / version | string | Name, display name, description, and version of the public item. |
status | string | importable, confirm_required, or not_importable. |
reason | string | Reason, e.g. same_collection_name_conflict or other_collection_name_conflict; empty when no reason applies. |
existingExtensionId | string / null | Conflicting enterprise extension ID; null when none exists. |
ConnectorConfig
| Field | Type | Description |
|---|---|---|
servers | object[] | Required; exactly one server. |
servers[].name | string | Required; non-empty after trimming whitespace. Control characters are not allowed. |
servers[].protocol | string | Required; supported values: streamable_http, sse, stdio. |
servers[].url | string | Required for streamable_http or sse. Must be an HTTPS URL without user information, query parameters (including an empty ?), or a fragment. Do not set for stdio. |
servers[].command | string | Required for stdio; cannot be blank or contain line breaks or null characters. Do not set for remote protocols. |
servers[].args | string[] | Optional command arguments for stdio only; do not configure for remote protocols. |
servers[].env | object | Optional environment variables with string values for stdio only. Credentials are not accepted; do not configure for remote protocols. |
servers[].enabled | boolean | Optional; defaults to true. |
servers[].runtimeKind | string | Optional; only client_direct is supported and is the default. |
servers[].description | string | Optional description. |
Download
| Field | Type | Description |
|---|---|---|
extensionId / version | string | Enterprise extension ID and version. |
downloadUrl | string | Temporary download URL. |
expiresAt | string | URL expiration timestamp in RFC3339 format. |
fileHash | string | Package file hash. |
fileSize | integer | Package size in bytes. |
ImportResult
| Field | Type | Description |
|---|---|---|
extensionId | string | Imported enterprise extension ID; retrieve its details for the full object. |
sourceId | string | Public source item ID. |
name / version | string | Imported item name and version. |
API reference
| API | Method | Relative path |
|---|---|---|
| List collections | GET | /extension-collections |
| Get a collection | GET | /extension-collections/{collection_id} |
| Create a collection | POST | /extension-collections |
| Update a collection | PUT | /extension-collections/{collection_id} |
| Delete a collection | DELETE | /extension-collections/{collection_id} |
| List extensions | GET | /extensions |
| Get an extension | GET | /extensions/{extension_id} |
| Upload and publish to a collection | POST | /extension-collections/{collection_id}/extensions |
| Update an extension package | PUT | /extensions/{extension_id}/package |
| Update extension metadata | PUT | /extensions/{extension_id}/metadata |
| Update extension distribution | PUT | /extensions/{extension_id}/distribution |
| Delete an extension | DELETE | /extensions/{extension_id} |
| List extension changelogs | GET | /extensions/{extension_id}/changelogs |
| Get an extension download URL | GET | /extensions/{extension_id}/download |
| List categories | GET | /extension-categories |
| Get a category | GET | /extension-categories/{category_id} |
| Create a category | POST | /extension-categories |
| Update a category | PUT | /extension-categories/{category_id} |
| Delete a category | DELETE | /extension-categories/{category_id} |
| Update multiple categories | PUT | /extension-categories |
| List public marketplace import candidates | GET | /extension-collections/{collection_id}/marketplace-items |
| Import a public marketplace item | POST | /extension-collections/{collection_id}/imports |
| Register a Connector | POST | /extension-collections/{collection_id}/connectors |
1. List collections
GET /v1/organizations/{organization_id}/apphub/extension-collections
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
keyword | string | No | Search keyword. |
type | string | No | Content type: plugin, skill, or connector. |
product | string | No | Product: qoder. |
visibilityScope | string | No | Visibility scope: none, all, or group. |
maxResults | integer | No | Page size, 1–100; default: 20. |
nextToken | string | No | Cursor returned by the previous page; omit for the first page. |
Request example
Success response (200 OK)
2. Get a collection
GET /v1/organizations/{organization_id}/apphub/extension-collections/{collection_id}
Request example
Success response (200 OK)
3. Create a collection
POST /v1/organizations/{organization_id}/apphub/extension-collections
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Non-empty name, up to 128 UTF-8 bytes. |
displayName | string | No | Display name, up to 256 UTF-8 bytes. |
description | string | No | Description, up to 2,048 UTF-8 bytes. |
type | string | Yes | plugin, skill, or connector; immutable after creation. |
products | string[] | Yes | Target products: ["qoder"]. Not editable through the update API. |
visibility | object | Yes | Marketplace visibility of the collection; see Visibility. |
Request body example
Request example
Success response (201 Created)
4. Update a collection
PUT /v1/organizations/{organization_id}/apphub/extension-collections/{collection_id}
Provide at least one field. Omitted fields are unchanged. type and products are immutable.
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | Non-empty; up to 128 UTF-8 bytes. |
displayName | string | No | Up to 256 UTF-8 bytes. |
description | string | No | Up to 2,048 UTF-8 bytes. |
visibility | object | No | New marketplace visibility; see Visibility. |
Request body example
Request example
Success response (200 OK)
5. Delete a collection
DELETE /v1/organizations/{organization_id}/apphub/extension-collections/{collection_id}
Only empty collections can be deleted. A non-empty collection returns 409.
Request example
Success response (204 No Content)
No response body.
6. List extensions
GET /v1/organizations/{organization_id}/apphub/extensions
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
collectionId | string | No | Filter by collection ID. |
keyword | string | No | Search keyword. |
type | string | No | plugin, skill, or connector. |
status | string | No | Extension status: active or archived. |
scope | string | No | Distribution scope: none, all, or group. |
contains | string | No | Package component type: hooks, skills, rules, mcp, agents, commands. Repeat the parameter for multiple values, e.g. contains=skills&contains=commands. |
maxResults | integer | No | Page size, 1–100; default: 20. |
nextToken | string | No | Cursor returned by the previous page; omit for the first page. |
Request example
Success response (200 OK)
7. Get an extension
GET /v1/organizations/{organization_id}/apphub/extensions/{extension_id}
Request example
Success response (200 OK)
8. Upload and publish to a collection
POST /v1/organizations/{organization_id}/apphub/extension-collections/{collection_id}/extensions
Each request accepts exactly one file part and one metadata part. The package must match the collection type. Register Connectors through the JSON endpoint. No separate upload URL or object-storage operation is required.
Request parameters
file and metadata are multipart fields. All remaining fields belong inside the metadata JSON, not in separate parts.
| Parameter | Type | Required | Description |
|---|---|---|---|
file | file | Yes | One Plugin or Skill ZIP, up to 50 MiB. |
metadata | object | Yes | JSON part with Content-Type application/json; use {} when no options are needed. Supports the metadata fields below. |
version | string | No | Optional Skill version; generated when omitted. Plugin versions come from the package manifest and cannot be overridden. |
categoryId | string | No | Enterprise category ID; omit to leave uncategorized. |
distribution | object | No | Distribution scope and client policy; defaults to none/configurable. See Distribution. |
changeNote | string | No | Change note, up to 1,024 UTF-8 bytes. |
Request example
Success response (201 Created)
9. Update an extension package
PUT /v1/organizations/{organization_id}/apphub/extensions/{extension_id}/package
Applies to Plugin and Skill. Preserves the extension ID, collection, category, and distribution. metadata does not accept categoryId or distribution. The example assumes the Skill is currently at version 1.0.0; omitting version upgrades it to 2.0.0.
Request parameters
file and metadata are multipart fields. All remaining fields belong inside the metadata JSON, not in separate parts.
| Parameter | Type | Required | Description |
|---|---|---|---|
file | file | Yes | One replacement ZIP, up to 50 MiB. |
metadata | object | Yes | application/json part supporting version and changeNote; use {} when empty. |
version | string | No | Skill only: specify a semantic version strictly greater than the current version. When omitted, the major version increases by one and minor and patch reset to zero, for example 1.0.0 becomes 2.0.0. Plugin versions come from the package manifest, must also be strictly greater than the current version, and cannot be overridden through metadata. |
changeNote | string | No | Change note, up to 1,024 UTF-8 bytes. |
Request example
Success response (200 OK)
10. Update extension metadata
PUT /v1/organizations/{organization_id}/apphub/extensions/{extension_id}/metadata
Provide at least one of displayName, description, categoryId, or config. Connector updates also require expectedRevision.
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
displayName | string | No | Display name. |
description | string | No | Description. |
categoryId | string / null | No | Category ID; null clears the category; omission preserves it. |
config | object | No | Connector only; see ConnectorConfig. |
expectedRevision | integer | No | Required for Connector updates: use the current revision (at least 1). Not accepted for Plugin or Skill. |
changeNote | string | No | Change note, up to 1,024 UTF-8 bytes. |
Request body example
Request example
Success response (200 OK)
11. Update extension distribution
PUT /v1/organizations/{organization_id}/apphub/extensions/{extension_id}/distribution
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
scope | string | Yes | none, all, or group. |
targetGroupIds | string[] | No | Required only for scope=group; up to 1,000 unique group IDs belonging to this organization. |
clientPolicy | string | No | Required unless scope=none: configurable, force_enabled, or silent_enabled. Omit with scope=none to preserve the existing policy. |
expectedRevision | integer | No | Required for Connector updates: use the current revision (at least 1). Not accepted for Plugin or Skill. |
changeNote | string | No | Change note, up to 1,024 UTF-8 bytes. |
Request body example
Request example
Success response (200 OK)
12. Delete an extension
DELETE /v1/organizations/{organization_id}/apphub/extensions/{extension_id}
Removes the enterprise extension and performs associated package cleanup. Recovery is not guaranteed. The public marketplace source is not deleted.
Request example
Success response (204 No Content)
No response body.
13. List extension changelogs
GET /v1/organizations/{organization_id}/apphub/extensions/{extension_id}/changelogs
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
maxResults | integer | No | Page size, 1–100; default: 20. |
nextToken | string | No | Cursor returned by the previous page; omit for the first page. |
Request example
Success response (200 OK)
14. Get an extension download URL
GET /v1/organizations/{organization_id}/apphub/extensions/{extension_id}/download
Plugin and Skill only; Skill downloads contain the original Skill ZIP. URLs are temporary. The response uses Cache-Control: no-store; do not cache or publicly share the URL.
Request example
Success response (200 OK)
15. List categories
GET /v1/organizations/{organization_id}/apphub/extension-categories
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
keyword | string | No | Search keyword. |
maxResults | integer | No | Page size, 1–100; default: 20. |
nextToken | string | No | Cursor returned by the previous page; omit for the first page. |
Request example
Success response (200 OK)
16. Get a category
GET /v1/organizations/{organization_id}/apphub/extension-categories/{category_id}
Request example
Success response (200 OK)
17. Create a category
POST /v1/organizations/{organization_id}/apphub/extension-categories
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Non-empty name, up to 128 UTF-8 bytes. |
sortOrder | integer | No | Sort order (int32). |
Request body example
Request example
Success response (201 Created)
18. Update a category
PUT /v1/organizations/{organization_id}/apphub/extension-categories/{category_id}
Provide at least one field. The system category uncategorized cannot be modified.
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | Non-empty category name, up to 128 UTF-8 bytes. |
sortOrder | integer | No | Sort order (int32). |
Request body example
Request example
Success response (200 OK)
19. Delete a category
DELETE /v1/organizations/{organization_id}/apphub/extension-categories/{category_id}
Returns 409 while referenced by extensions; update their categories first. The system category uncategorized cannot be deleted.
Request example
Success response (204 No Content)
No response body.
20. Update multiple categories
PUT /v1/organizations/{organization_id}/apphub/extension-categories
Batch operations apply only to category management, not ZIP uploads. Unspecified categories are not deleted.
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
items | object[] | Yes | 1–1,000 items with unique IDs; only specified items are updated. |
items[].id | string | Yes | Category ID; cannot be uncategorized. |
items[].name | string | No | Non-empty, up to 128 UTF-8 bytes. Provide name or sortOrder, or both. |
items[].sortOrder | integer | No | Sort order (int32). |
Request body example
Request example
Success response (200 OK)
21. List public marketplace import candidates
GET /v1/organizations/{organization_id}/apphub/extension-collections/{collection_id}/marketplace-items
Lists candidates for the target collection type. Check status, reason, and existingExtensionId before importing.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
keyword | string | No | Search keyword. |
maxResults | integer | No | Page size, 1–100; default: 20. |
nextToken | string | No | Cursor returned by the previous page; omit for the first page. |
Request example
Success response (200 OK)
22. Import a public marketplace item
POST /v1/organizations/{organization_id}/apphub/extension-collections/{collection_id}/imports
Imports one item per request. overwrite may create a new enterprise extension ID and does not preserve the old distribution policy; the default scope is none. The public source remains unchanged. After failure or timeout, query the collection before retrying.
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
sourceType | string | Yes | qoder_plugin or qoder_skill. |
sourceId | string | Yes | Public item ID from the candidate list. |
categoryId | string | No | Enterprise category ID; omit to leave uncategorized. |
onConflict | string | No | reject (default) or overwrite. |
Request body example
Request example
Success response (201 Created)
23. Register a Connector
POST /v1/organizations/{organization_id}/apphub/extension-collections/{collection_id}/connectors
The collection type must be connector. Uses JSON, not a ZIP upload. OAuth credentials and authentication configurations are not accepted.
Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Connector identifier, up to 128 UTF-8 bytes. Use lowercase letters and digits, optionally separated by single hyphens, for example docs-search. Leading, trailing, and consecutive hyphens are not allowed. Use displayName for the human-readable name. |
displayName | string | Yes | Display name. |
description | string | No | Description. |
categoryId | string | No | Enterprise category ID; omit to leave uncategorized. |
config | object | Yes | See ConnectorConfig; exactly one server is required. |
distribution | object | No | Distribution scope and client policy; defaults to none/configurable. See Distribution. |
changeNote | string | No | Change note, up to 1,024 UTF-8 bytes. |
Request body example
Request example
Success response (201 Created)
Upload limits
Each request accepts one ZIP containing one standalone Plugin or Skill. A Plugin can contain multiple nested Skills. The file limit is 50 MiB; the full multipart request limit is 51 MiB. Uncompressed ZIP contents are limited to 256 MiB and 10,000 entries. Path traversal, symbolic links, corrupted packages, and batches of independent extensions are rejected. Use the management endpoints to change categories or distribution separately from a package update.
ZIP directory structure
A standalone Skill must contain exactly one top-level directory. Its directory name becomes the Skill name, and it must contain a case-sensitive SKILL.md file. Do not place SKILL.md directly at the ZIP root. The archive must contain exactly one SKILL.md and must not contain .qoder-plugin/plugin.json. For example:
code-review to preserve this layout:
.qoder-plugin/plugin.json, either at the ZIP root or inside a single top-level directory. If a wrapper directory is used, all package files must be inside it; additional wrapper levels are not supported. This example uses the ZIP root:
name in plugin.json supplies the Plugin name, and version supplies its package version. Updates require a version strictly greater than the current version. Minimal example:
.qoder-plugin. Include any additional skills, commands, or other Plugin files in the archive as well. Nested Skills within a Plugin do not count as multiple independent uploads.
Error responses
Errors follow the OpenAPI conventions. Keep requestId for troubleshooting. Common HTTP statuses are listed below; use the actual code from the response.
| HTTP | Description |
|---|---|
| 400 | Invalid parameters, package format, or type; missing Connector expectedRevision. |
| 401 | Authentication failed. |
| 403 | Organization mismatch, capability unavailable, or insufficient permissions. |
| 404 | Collection, extension, category, or import source not found. |
| 409 | Name/version conflict; non-empty collection or referenced category cannot be deleted; import conflict. |
| 413 | Upload request or file exceeds the size limit. |
| 429 | Too many requests or upload concurrency limit reached. |
| 500 | Internal server error. |
| 503 | A dependent service is temporarily unavailable. |