GreenPT Docs

Skills

Named, reusable instruction sets applied server-side to chat completions.

POST

A skill is a named set of instructions stored with your account. Naming it on a chat completion request makes GreenPT apply those instructions on the server, so a prompt you refined once can shape any conversation without your client resending — or even storing — it.

Skills created through this API are the same skills the GreenPT Apps chat offers in its composer — create one in either place and use it from both.

Skills are not an agent's tools

Agents come with tools — web search, vision, transcription — fixed capabilities of the agent runtime. The skills on this page are instruction sets you write and manage yourself, and they are one of the parts you compose an agent from: naming a skill when starting an agent run applies its instructions to the run, the same way it shapes a chat completion.

Applying a skill

Set the skills body field on a chat completion request to a single skill name:

{
  "model": "gemma4",
  "messages": [
    { "role": "user", "content": "Draft a launch email for our reporting feature." }
  ],
  "skills": "email-writer"
}

The field takes one skill per request — there is no list form. It works on streaming and non-streaming requests alike, and with every model id, including the compression models: the compressed style and your skill's instructions both apply.

curl https://api.greenpt.ai/v1/chat/completions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gemma4",
    "messages": [{ "role": "user", "content": "Draft a launch email for our reporting feature." }],
    "skills": "email-writer"
  }'
const response = await fetch('https://api.greenpt.ai/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.GREENPT_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    model: 'gemma4',
    messages: [{ role: 'user', content: 'Draft a launch email for our reporting feature.' }],
    skills: 'email-writer',
  }),
});

With the OpenAI SDK, pass the field via extra_body:

client.chat.completions.create(
    model="gemma4",
    messages=[{"role": "user", "content": "Draft a launch email for our reporting feature."}],
    extra_body={"skills": "email-writer"},
)

Pinning a version

The field also takes an object form that pins one of the skill's published versions:

{ "skills": { "name": "email-writer", "version": 3 } }

Each write that changes a skill's instructions publishes the next version — the skill object's version field names the latest — and earlier versions stay addressable after further edits. A bare name always applies the latest content; pin when a request must not move with edits. Resolution is unchanged: the name picks the skill exactly as described below, and the pin only picks which published revision is injected. A version the skill does not have fails the request with a 400 carrying reason: "unknown_version" — see Errors.

Every published version records a digest of its instruction bytes, reported as X-Skill-Content-Hash on any response the skill shaped. Pinning a version and comparing that digest across responses are the two ways to establish that a result came from the instructions you think it did.

How a skill is applied

  1. Resolve — the name is matched against your own skills first, then your organisation's, then skills GreenPT provides. The first match wins, so your skill shadows a GreenPT-provided one of the same name. Matching is exact and case-sensitive, and only enabled skills resolve; a name that matches nothing fails the request with a 400.
  2. Inject — the resolved instructions join the request's system message, wrapped in a <user_skill> block placed after your own system prompt. The wrapper frames the block as guidance you chose for the request: it stays subordinate to the instructions outside it. A request without a system message gets one holding the block.
  3. Stripskills is a GreenPT-only field; it is removed before the request reaches the model provider.
  4. Bill — the injected block's tokens are input tokens like any others. They are included in the usage the response reports and billed accordingly; the X-Skill-Injected-Tokens header tells you exactly how many.

Response headers

When a skill resolves, the response reports what was applied, on streaming and non-streaming responses alike:

HeaderMeaning
X-SkillThe applied skill's name.
X-Skill-Injected-TokensTokens the injected block added to your prompt, counted with the wrapper included.
X-Skill-Content-HashDigest of the skill's instructions — the authored bytes alone, without the wrapper the token count includes. Two responses carrying the same digest were shaped by identical instructions, whatever the skill was called or renamed to.
X-Skill-VersionThe published version whose bytes were injected.

The first three accompany every resolved skill. X-Skill-Version is conditional: it appears only when the injected bytes are a numbered published version, and is absent when they are not — a skill written before versions existed, or one whose stored content no longer matches the version it records. The hash describes the bytes in either case, which is why it, rather than the version, is the field to compare when you need to know whether two responses got the same instructions.

No X-Skill* header appears when the request carries no skills field.

Managing skills

Skills are created and maintained at /v1/skills, authenticated with your API key like every other endpoint. Skills you create through it belong to you; your organisation's skills and the GreenPT catalog are visible but not editable.

The skill object

FieldTypeDescription
idstringUnique identifier, used in the management routes.
namestringThe name used in the skills request field.
descriptionstringWhen GreenPT should use the skill, phrased as a condition; shown wherever skills are picked, such as the GreenPT Apps switcher.
instructionsstringThe instructions injected when the skill is applied. Detail responses only.
enabledbooleanWhether the skill resolves on chat completions.
scopestringWho provides it: user (you), organisation, or greenpt.
versionintegerPublished revisions of the instructions: 1 on create, one higher after each write that changes them. null on skills last written before versions existed.
createdAtstringISO 8601 timestamp.
updatedAtstringISO 8601 timestamp.

List skills

GET /v1/skills

Returns { "skills": [...] } with every skill visible to you, sorted by name: your own (enabled or not), plus enabled skills from your organisation and from GreenPT. List entries omit instructions; fetch a skill by id for the full object. The same name can appear more than once when it exists in more than one scope — the resolution order above decides which one a chat completion uses. The list is not paginated.

Get a skill

GET /v1/skills/{id}

Returns the full skill object, including instructions.

Create a skill

POST /v1/skills
FieldTypeRequiredDescription
namestringYesStarts with a letter or digit; letters, digits, hyphens, underscores; max 64 characters.
descriptionstringNoMax 500 characters. State when the skill should be used.
instructionsstringYes1 to 20,000 characters. Markdown welcome — it reaches the model as written.

Returns 201 with the skill object. A skill is enabled from the moment it is created; switch it off with a PATCH if it is not ready for use. Names are unique among your skills, and picking the same name as an organisation or GreenPT skill is allowed — yours takes precedence when applied.

curl https://api.greenpt.ai/v1/skills \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "release-notes",
    "description": "Use when announcing shipped features to customers — release notes, changelog entries, launch emails.",
    "instructions": "Write release notes as short, benefit-first bullets. Name the feature, say what it lets the reader do, skip internal jargon."
  }'

Update a skill

PATCH /v1/skills/{id}

Accepts any of name, description, instructions, and enabled — at least one. Only your own skills can be updated. Returns 200 with the updated object. A patch that changes instructions publishes the next version and raises version by one; a patch that touches only the other fields, or saves instructions identical to the active version, publishes nothing.

Delete a skill

DELETE /v1/skills/{id}

Returns 204. Deleting is permanent: the skill disappears from your list and its name becomes free for reuse. Its published versions go with it, so a request pinning one fails as an unknown skill, and a later skill created under the same name starts again at version 1. To retire a skill you might want back, set enabled to false instead:

AfterDELETEPATCH {"enabled": false}
Listed in GET /v1/skillsNoYes
Readable and editableNoYes
Resolves on completionsNoNo
Name reusableYesNo

Errors

The two surfaces answer differently. A skills failure on a chat completion returns the same JSON error envelope as any other completion error, so an OpenAI-compatible SDK surfaces it the way it surfaces the rest. The management routes under /v1/skills return plain text.

On a chat completion

{
  "error": {
    "message": "Invalid \"skills\" parameter: Unknown version 99 of skill \"email-writer\".",
    "type": "invalid_request_error",
    "param": "skills",
    "code": "skill_unresolved",
    "reason": "unknown_version"
  }
}

Every such failure is a 400. Branch on code and reason rather than matching the message, which is prose and may be reworded:

codereasonWhen
skill_param_invalidskills is neither a non-empty name nor { name, version } with a positive integer version.
skill_unresolvedunknownNo skill of that name is visible to you — including one you deleted.
skill_unresolveddisabledThe skill exists and you can see it, but enabled is false.
skill_unresolvedunknown_versionThe skill resolved; the pinned version was never published.
skill_unresolvedunversionedThe skill resolved but has no addressable versions, so it cannot be pinned.

reason is present only on skill_unresolved. Disabling and deleting are distinguishable on purpose: both stop a skill resolving, and only one of them is something you can undo, so a client can tell a user which happened.

Authentication, credit, and rate-limit errors are checked before the skills field, so their status codes take precedence over any of the above.

On the management routes

Plain text, with one exception: instructions past the character cap answer with JSON, because "too long" is a limit a client reacts to programmatically rather than shows verbatim.

{
  "error": {
    "message": "Instructions are 24310 characters; the limit is 20000.",
    "code": "instructions_too_long",
    "limit": 20000,
    "actual": 24310
  }
}

Read limit from the response rather than hard-coding it, so a client does not go stale the first time the cap moves.

StatusWhenBody
400instructions are past the capThe JSON above, carrying code, limit and actual.
400A create or update body fails validation otherwiseInvalid skill: (create) or Invalid skill patch: (update), followed by one line per issue.
401Missing or invalid API keyUnauthorized
404The id does not exist, or names a skill you cannot see or editSkill not found
409The name is already taken among your skillsYou already have a skill named "<name>".
503Skills are not switched on in this environmentSkills are not enabled

The 503 is checked before your API key is, so it answers an unauthenticated request too — which makes it a free way to ask an environment whether skills are switched on at all.

Limits

WhatLimit
Skills per request1
Name64 characters
Description500 characters
Instructions20,000 characters

On this page