| name | api-use-case |
| description | Add REST API coverage to an existing Customer Portal article with MethodSwitch and repository OpenAPI specs. Use when asked to add API coverage or create a REST API tab for a portal-only article. |
Read an existing Customer Portal article, find the matching API endpoints in the
OpenAPI spec, and write a complete <MethodSection id="api"> section.
Scope — read exactly these files
- This SKILL.md
- The existing article specified by the user
/api-reference/services_documented/{product}_api.yaml — the relevant OpenAPI spec
.agents/references/mdx-rules.md — MethodSwitch structure rules
.agents/references/style-guide.md — writing rules for the API section prose
.agents/references/procedures.md — step format and ordering rules
Do not read other articles unless the existing article cross-links to them and
the link is directly relevant to mapping a Portal step to an API call.
Inputs
| Input | Required | Notes |
|---|
| Path to existing Portal article | Yes | The article that will receive the API tab |
| Real API testing | No | Ask the user — see Phase 2 |
Phase 1 — Read the Portal article
Read the full article. Understand:
- What resources are created and in what order — e.g. network → subnet → instance → floating IP
- Dependencies between steps — e.g. subnet needs
network_id from step 2
- The overall flow type:
- Steps feed into each other → sequential → use Structure A (Quickstart + Step-by-step)
- Steps are independent operations → independent → use Structure B (standalone sections)
Rule: "Create" / "Deploy" articles are almost always sequential (Structure A).
"Manage" / "Configure" articles are almost always independent (Structure B).
Test: "Can the user do Step 3 without Steps 1 and 2?" If yes → Structure B.
- Identify the product from the file path — determines which OpenAPI YAML to load.
Phase 2 — API testing (ask the user)
Before writing, ask:
The API section can be based on the OpenAPI spec alone, or I can also run
the actual API calls against the live environment to verify real responses,
catch undocumented errors, and confirm field behavior.
- Yes, test with real API — more accurate, takes longer
- No, use the spec only — faster, mark uncertain steps as
{TODO: verify}
Wait for the answer before proceeding.
If yes → real testing:
- API credentials are in
C:\Projects\docops-agent2\access.md
- Use Luxembourg-3 (
region_id: 148) for general VM and networking
- Use Frankfurt-2 (
region_id: 180) for DBaaS and Kubernetes
- Run each API call end-to-end in the terminal using
curl against https://api.gcore.com
- Record real responses — exact fields, structure, error messages
- Also run every Python SDK and Go SDK code sample — install the SDK in
venv, execute each snippet against the live API, confirm it runs without errors and returns real data
- Python SDK:
pip install gcore in venv; Go SDK: go run in a temp module
- SDK field names (method names, struct fields, response object attributes) must match the actual SDK — never extrapolate or guess them
- Only after the full flow runs end-to-end for BOTH curl AND SDK — move to Phase 3
- Delete test resources immediately after each test, not at the end
If no → spec only:
- Mark steps that cannot be verified from the spec as
{TODO: verify in live environment}
- These will need a follow-up
full-audit run to verify
Phase 3 — Find the API endpoints
Open the OpenAPI YAML for the product:
/api-reference/services_documented/{product}_api.yaml
For each Portal step, find the equivalent API operation:
# Search for an endpoint by path pattern
Select-String -Path "api-reference\services_documented\cloud_api.yaml" `
-Pattern "/cloud/v\d+/instances"
Mapping rules:
- Always use the latest non-deprecated API version (
v2 over v1 when both exist)
- Verify field names from the YAML spec — never write field names from memory
- Note gotchas discovered: wrong methods, missing required fields, non-obvious validation
Known gotchas to check before writing (add new ones as you discover them):
GET /cloud/v1/instances/{project_id}/{region_id}/available_flavors is a POST requiring volume data — use GET /cloud/v1/flavors/{project_id}/{region_id} to list flavors
- Instance
name field is blocked on reseller accounts — standard accounts support it normally; use name in article examples
addresses field uses network name as key (e.g. "pub_net"), not a fixed string — iterate over all keys
SDK code patterns (mandatory — do not deviate)
These patterns are canonical. Every Python and Go example must follow them exactly.
Deviating from these patterns requires fixing all examples retroactively.
Python SDK
The SDK constructor Gcore() reads three environment variables automatically:
GCORE_API_KEY → api_key
GCORE_CLOUD_PROJECT_ID → cloud_project_id
GCORE_CLOUD_REGION_ID → cloud_region_id
Because cloud_project_id and cloud_region_id are set at client level, they must not be passed to individual API calls. Any method that accepts project_id and region_id as optional parameters will use the client-level values when they are omitted.
Correct pattern:
from gcore import Gcore
client = Gcore()
clusters = client.cloud.gpu_virtual.clusters.list()
cluster = client.cloud.gpu_virtual.clusters.get("{ID}")
When import os is needed: only when the code reads additional env vars that the SDK does not handle automatically — for example GCORE_SSH_KEY_NAME, GCORE_CLUSTER_NAME, CLUSTER_NAME. Do not import os just for GCORE_CLOUD_PROJECT_ID or GCORE_CLOUD_REGION_ID.
Never write:
client = Gcore(api_key=os.environ["GCORE_API_KEY"])
client = Gcore(api_key=os.environ.get("GCORE_API_KEY"))
project_id = int(os.environ["GCORE_CLOUD_PROJECT_ID"])
region_id = int(os.environ["GCORE_CLOUD_REGION_ID"])
client.cloud.something.list(project_id=project_id, region_id=region_id)
Go SDK
The Go SDK does not read project_id/region_id at client level — they must be passed explicitly in every params struct. Only GCORE_API_KEY is read automatically by gcore.NewClient().
Correct pattern:
import (
"context"
"os"
"strconv"
gcore "github.com/G-Core/gcore-go"
"github.com/G-Core/gcore-go/cloud"
)
func main() {
projectID, _ := strconv.ParseInt(os.Getenv("GCORE_CLOUD_PROJECT_ID"), 10, 64)
regionID, _ := strconv.ParseInt(os.Getenv("GCORE_CLOUD_REGION_ID"), 10, 64)
client := gcore.NewClient()
ctx := context.Background()
result, err := client.Cloud.Something.Do(ctx, cloud.SomeParams{
ProjectID: gcore.Int(projectID),
RegionID: gcore.Int(regionID),
})
}
Never write:
import "github.com/G-Core/gcore-go/option"
client := gcore.NewClient(option.WithAPIKey(os.Getenv("GCORE_API_KEY")))
result, err := client.Cloud.Something.Do(context.TODO(), ...)
Key rules:
gcore.NewClient() — no arguments; never pass option.WithAPIKey
- No
option import
ctx := context.Background() — one variable, reused in all calls
context.TODO() is forbidden
Phase 4 — Write the API section
Structure A — Sequential creation flow
Use when Portal steps must execute in order and outputs feed into later steps.
Opening sentence (required, before <Info> block):
One sentence describing what this section enables. Rules:
- Do NOT start with "The steps below..."
- Do NOT explain what the product is (that belongs in an overview article)
- DO describe the user action and outcome
Good: "Create a subnet inside an existing network and customize its DHCP and DNS settings."
Good: "Keep services reachable even when infrastructure changes by assigning a floating IP."
Bad: "The steps below create a subnet and configure DNS." ← starts with "The steps below"
<Info> block (required):
<Info>
An [API token](/account-settings/api-tokens) is required, along with a
[project ID](/api-reference/cloud/projects/list-projects)
and a [region ID](/api-reference/cloud/regions/list-regions).
</Info>
If the flow requires an existing resource (e.g. a network), add it:
"...and the ID of an existing [network](/cloud/networking/create-and-manage-a-network)."
Environment variables block:
Open a terminal and set these environment variables before running the examples:
```bash
export GCORE_API_KEY="{YOUR_API_KEY}"
export GCORE_CLOUD_PROJECT_ID="{YOUR_PROJECT_ID}"
export GCORE_CLOUD_REGION_ID="{YOUR_REGION_ID}"
**Quickstart section:**
```mdx
## Quickstart
{One sentence: what the scripts do. Not "Complete scripts for the full flow."}
<Tabs>
<Tab title="Python SDK">
```python
# Step 1. {action}
# Step 2. {action}
```
</Tab>
<Tab title="Go SDK">
```go
// Step 1. {action}
// Step 2. {action}
```
</Tab>
</Tabs>
Quickstart rules:
Step-by-step section:
## Step-by-step
<p>Each step below explains what the call does, which parameters matter,
and what the response looks like.</p>
<Accordion title="Show all steps">
### Step 1. {Verb + object}
{One sentence: why this step matters in the flow.}
| Parameter | Required | Description |
|-----------|----------|-------------|
| `param` | Yes | What it does and how to get it |
<Tabs>
<Tab title="Python SDK">```python ... ```</Tab>
<Tab title="Go SDK">```go ... ```</Tab>
<Tab title="curl">
```bash
curl -X POST "https://api.gcore.com/..." \
-H "Authorization: APIKey $GCORE_API_KEY" \
-d '{...}'
```
Response:
```json
{"tasks": ["abc-123"]}
```
</Tab>
</Tabs>
The API returns:
```json
{"tasks": ["abc-123"]} // save as TASK_ID
```
Step anatomy rules:
- One prose sentence: why this step matters — not "In this step, you will..."
- Parameters table: non-obvious required fields only
- Code tabs: Python SDK → Go SDK → curl (curl always last)
- Response JSON: always labeled with
The API returns: — never a bare JSON block after </Tabs>
- Inline API reference link: embed in a meaningful sentence, not standalone
Polling pattern — when an endpoint returns {"tasks": [...]}:
Run <code>GET /cloud/v1/tasks/{task_id}</code> every 5 seconds until
`state` is `FINISHED`, then read the resource ID from `created_resources`.
While provisioning:
```json
{"state": "RUNNING", "created_resources": {}}
When complete:
{"state": "FINISHED", "created_resources": {"instances": ["abc-123"]}}
**Optional standalone sections** (after the accordion, as separate `##` headings):
- `## Add an SSH key`, `## Filter images by OS`, `## Attach to a private network`
- `## Clean up` — always standalone, never inside the accordion
**Forbidden sections:** `## Prerequisites`, `## Next steps`, `## What's next`, `## Requirements`
---
### Structure B — Independent operations
Use when operations are unrelated and can be done in any order (manage/configure articles).
```mdx
{Opening sentence}
<Info>...</Info>
```bash
export GCORE_API_KEY="{YOUR_API_KEY}"
...
{Operation name}
{One sentence.}
```python ... ```
```go ... ```
```bash ... ```
The API returns:
{...}
{Another operation name}
No Quickstart section for Structure B — operations are unrelated and a single
script would be artificial.
---
## Phase 5 — Wrap in MethodSwitch
If the article currently has no MethodSwitch, wrap the existing portal content:
```mdx
import { MethodSwitch, MethodSection } from "/snippets/method-switch.jsx";
<MethodSwitch>
<MethodSection id="portal" label="Customer Portal">
{existing portal content — do not change it}
</MethodSection>
<MethodSection id="api" label="REST API">
{new API section written in Phase 4}
</MethodSection>
</MethodSwitch>
If MethodSwitch already exists, add the <MethodSection id="api"> after the portal section.
Do not modify the portal section — it is out of scope for this skill.
Phase 6 — Update frontmatter
Update ai-navigation to mention both methods:
ai-navigation: Create Linux Virtual Machines in Gcore Cloud via Customer Portal
by configuring image, flavor, and network, or via REST API using the Instances API.
Rules: one sentence, starts with action verb, mentions both methods, max 160 chars,
no curly braces, no URL paths, no colons followed by space.
Phase 7 — Review
Standalone tab test (run this first)
Each tab must work as a complete standalone article. The user reads only one tab —
never both. Apply this test before any other check:
Mentally delete the Portal tab. Can the API tab reader:
- Understand what this feature/resource is and why they would manage it?
- Find the right resource, ID, or identifier without switching tabs?
- Understand the consequence of each action (what enabling/disabling/creating/deleting does)?
- Complete the full task end-to-end?
If any answer is no — the API tab is incomplete. Fix before proceeding.
Common failures:
- Feature description exists only in the Portal tab intro → add a one-sentence context to the API tab opener
- Reference table (IDs, names, states) exists only in the Portal tab → add a compact version to the API tab (accordion is fine)
- Policy/resource descriptions exist only in the Portal tab → add a Purpose column to the API reference table
- Portal tab explains "what each option does", API tab only shows the call → API tab user can't make an informed decision
The goal is not two complementary tabs — it is two independent implementations of the same task.
Run these checks before showing the result:
Structure:
grep "^## What \|^## How \|^## Why \|^## When "
grep "^## Next steps\|^## Prerequisites\|^## Requirements\|^## See also"
grep "^This guide covers\|^This article\|^In this \|^The steps below"
Scan every ## and ### — verify a prose sentence follows before any code block or table.
Formatting:
- Bold only for UI elements — not for emphasis
- Em-dashes spaced:
— not —
- Response JSON never appears directly after
</Tabs> without a label
- Quickstart scripts have no combined step labels (
# Step 3+4)
- Flavor and image IDs not hardcoded — selected dynamically
Links:
- Link text 1–2 words maximum
- Each URL linked only once — subsequent mentions plain text
- No standalone "For more details, see..." sentences
Voice:
- No "you" or "your" in prose
- No forbidden words: just, simply, obviously, ensure, platform
MDX:
- Import has
.jsx extension
<MethodSwitch> wraps both sections
- Closing
</MethodSection> tags at column 0 after lists
- No
{identifier} in inline backtick spans
Output
Show the complete updated article. Then:
Article: [path]
API structure: [A — sequential / B — independent]
Steps covered: [N]
Real API tested: [yes / no — N steps marked TODO]
TODO items:
- {TODO: verify ...} at Step N — [what to check]
When the user confirms the result looks good — load .agents/skills/pr/SKILL.md
to create the branch, commit, and open a draft PR.
Terminology rule
Never use the word permanent when referring to API tokens. The expiration is user-controlled.
Always write: An [API token](/account-settings/api-tokens) is required.