Skill object
Create, get, and list endpoints return this structure. content and content_encoding are included only when Get a Skill is called with include_content=true.
| Field | Type | Description |
|---|
id | string | Skill ID with the skill_ prefix |
type | string | Always "skill" |
display_title | string | Display title, which can be set independently of name in the Skill package. Duplicate values are allowed for the same user |
source | string | Skill source: custom or qoder |
latest_version | string | null | Latest version identifier with three states: • After versioning, a Unix microsecond timestamp such as 1759178010641129; • During migration before backfill, a legacy incremental counter such as 2; • null after all versions are deleted. It is not guaranteed to be incrementable with +1; clients must not use it to predict the next version |
metadata | object | Custom metadata stored with the Skill; defaults to {} when omitted. created_by is reserved by Forward and must not be supplied by callers |
created_at | string | Creation time in RFC 3339 format |
updated_at | string | Last update time in RFC 3339 format |
identity_id | string | null | Owning Forward identity. Returns the Identity ID when owned by an Identity; otherwise null. See Identity ownership |
icon_url | string | null | Icon URL associated by Forward |
binding_info | Binding info | Binding information, such as Template reference counts |
description | string | Skill description. ⚠️ Deprecated: New clients should read it from the version object; see Skill version object. Retained only for compatibility |
content | string | ⚠️ Deprecated: Base64-encoded zip content returned with include_content=true. Use the version-content download endpoint instead |
content_encoding | string | ⚠️ Deprecated: Returned with content and always set to "base64". Use the version-content download endpoint instead |
Identity ownership
An account (or Workspace) can have multiple Identities. Each Identity represents an end user of a product integrated with that account (or Workspace).
A Skill can belong to an account (or Workspace) or to an Identity. A Skill Version always inherits ownership from its Skill and cannot have a separate owner. Ownership determines who can view and operate on the Skill and its versions.
Specifying ownership
| Caller | Ownership | How to specify |
|---|
| PAT | Account / Workspace | Omit identity_id (the default, unchanged from previous behavior) |
| PAT | Specified Identity | Supply the query parameter identity_id=<identity_id> |
| SAT (administrator) | Workspace | Resolved automatically; cannot be switched with a parameter |
| SAT (bound to an Identity) | That Identity | Resolved automatically; cannot be switched with a parameter |
identity_id is optional and is only used when operating on Identity-owned resources. A PAT can explicitly supply it; omitting it uses the administrator scope. For SATs, ownership is determined by the credential. To use Identity scope, issue an Identity-scoped credential and do not explicitly supply this parameter, even with an empty value. Any SAT, including Admin SAT, that supplies it receives HTTP 400.
The Identity specified by a PAT must belong to the account or Workspace represented by that PAT and must be enabled. An Identity that does not exist, is disabled or deleted, or does not belong to the caller returns 404.
Ownership isolation
- The administrator scope (a PAT without
identity_id, or an Admin SAT) cannot see Identity-owned Skills.
- An Identity cannot see Skills owned by the account (or Workspace) itself or by other Identities in the same account.
- Within a valid Identity scope, cross-scope attempts to retrieve, download, update, or delete resources all return
404, without distinguishing between nonexistent resources and resources owned by someone else.
- PATs without
identity_id and Admin SATs retain the existing behavior: an owner mismatch returns 403. Downstream permission checks may also return 403.
- Creation idempotency keys are isolated by ownership. Different Identities can reuse the same
Idempotency-Key without replaying each other's requests.
Supported endpoints
Creating, searching, listing, retrieving, updating, and deleting Skills, as well as listing, creating, retrieving, downloading content from, and deleting Skill Versions, all support Identity scope.
GET /api/v1/forward/resources/batch does not support identity_id; its visibility rules remain unchanged.
Skill version object
List Skill Versions, Get a Skill Version, and Create a Skill Version return this structure. Retrieve version content with Download Skill Version Content, and soft-delete an existing version with Delete a Skill Version. A version is immutable after creation and is an independent snapshot of content and metadata.
| Field | Type | Description |
|---|
id | string | Skill Version ID with the skillver_ prefix |
skill_id | string | Owning Skill ID |
version | string | Version number. New versions return a 16-digit Unix microsecond timestamp; the three states of latest_version use the same value domain |
name | string | Canonical name parsed from name in the uploaded package's SKILL.md frontmatter |
description | string | Description parsed from description in the uploaded package's SKILL.md frontmatter |
directory | string | Top-level package directory name, equal to name |
content_size | integer | Version package size in bytes, up to 50 MB |
content_sha256 | string | SHA-256 digest of the version package content, in lowercase hexadecimal |
status | string | Version status; currently always active |
created_at | string | Creation time in RFC 3339 format |
metadata | object | Version-level metadata; currently always {} |
Legacy version-number compatibility
The {version} path segment in GET /skills/{id}/versions/{version} accepts two forms:
- Recommended: A 16-digit Unix microsecond timestamp such as
1759178010641129.
- Compatibility: Legacy incremental counters such as
"1" or "2" still resolve to the same version during migration. A nonexistent legacy number returns 404.
The literal latest is not supported and returns 400 invalid_request_error. Call Get a Skill, read latest_version, and use that value in the path. Both Download Skill Version Content and Get a Skill Version follow this rule.
Binding info
Reference summary included by Forward in Skill responses.
| Field | Type | Description |
|---|
agent_template_count | integer | Number of Templates currently bound to the Skill |
Skill package (upload rules)
Create a Skill and Create a Skill Version upload content with multipart/form-data. The recommended field is files. file is retained for compatibility (⚠️ deprecated; matching requests return Deprecation: true in the response headers). Migrate to files.
Two upload forms are supported:
① Single .zip archive
Upload one files part with Content-Type: application/zip. See "Package structure example" below.
② Bare file tree
The files field can appear multiple times, with one file per part. Each part's filename carries a relative path, such as code-review/SKILL.md or code-review/scripts/run.sh. Forward normalizes parts independently of their order, packages them, and stores the result as a zip archive.
| Rule | Description |
|---|
| Upload field | files (repeatable parts); file (deprecated compatibility field for one zip archive) |
| Size limit | Both the compressed archive and its extracted contents must be no larger than 50 MB. Exceeding either limit returns 400 skill_content_too_large. Both the declared extracted size and actual extracted bytes are validated |
| Package manifest | Required SKILL.md under the top-level directory, with name and description in its frontmatter |
| Top-level directory | Exactly one; its directory name must match name in SKILL.md |
Package structure example
code-review/
├── SKILL # Required; includes frontmatter name/description
├── scripts/
│ └── run.sh
└── data/
└── rules.json
Skill list responses use the same pagination fields as other Forward list endpoints.
| Field | Type | Description |
|---|
data | array of Skill objects | Records on the current page |
has_more | boolean | Whether another page is available |
next_page | string | null | Forward cursor for the next page (recommended). Equals the current page's last_id when has_more=true; otherwise null |
first_id | string | null | ID of the first record on the current page |
last_id | string | null | ID of the last record on the current page |
The request cursor parameters page, after_id, and before_id are mutually exclusive; providing more than one returns 400. Use page where possible; it has the same semantics as after_id.
Skill version list responses use only the recommended pagination fields.
| Field | Type | Description |
|---|
data | array of Skill version objects | Records on the current page |
has_more | boolean | Whether another page is available |
next_page | string | Forward cursor for the next page; omitted when has_more=false |