# Writing Openapi Specs

> Reference guide for OpenAPI specification best practices, naming conventions, and expressing complex REST API patterns like polymorphism, enums, file uploads, and server-sent events. Use when writing or improving OpenAPI specs to ensure they follow established conventions and generate quality SDKs.

- **Type:** Skill
- **Install:** `agentstack add skill-speakeasy-api-skills-writing-openapi-specs`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [speakeasy-api](https://agentstack.voostack.com/s/speakeasy-api)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [speakeasy-api](https://github.com/speakeasy-api)
- **Source:** https://github.com/speakeasy-api/skills/tree/master/skills/writing-openapi-specs
- **Website:** https://www.speakeasy.com/docs

## Install

```sh
agentstack add skill-speakeasy-api-skills-writing-openapi-specs
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Writing OpenAPI Specs

Reference for OpenAPI best practices and conventions. This skill provides guidance on taste, conventions, and grey areas not covered by the OpenAPI specification itself.

## When to Use This Skill

- Writing a new OpenAPI specification
- Improving operation naming and organization
- Expressing complex data types (enums, polymorphism, nullable)
- Handling file uploads, streaming, or server-sent events
- Making specs more code-gen friendly
- Understanding reusability patterns (components vs inline)

## Core Principles

### Naming Conventions

**Operation IDs**: Use lowercase with underscores, following `resource_action` pattern:

```yaml
# Good
operationId: users_list
operationId: users_get
operationId: users_create

# Avoid
operationId: GetApiV1Users    # Auto-generated, not semantic
```

**Component Names**: Use PascalCase for schemas, parameters, and other reusable components:

```yaml
components:
  schemas:
    UserProfile:      # PascalCase
    OrderHistory:     # PascalCase
  parameters:
    PageLimit:        # PascalCase
```

**Tags**: Use lowercase with hyphens for machine-friendly tags:

```yaml
tags:
  - name: user-management
    description: Operations for managing users
  - name: order-processing
    description: Operations for processing orders
```

For more details, see [reference/operations.md](reference/operations.md) and [reference/components.md](reference/components.md).

### Documentation Standards

**Use CommonMark**: All `description` fields support CommonMark syntax for rich formatting:

```yaml
description: |
  Retrieves a user by ID.

  ## Authorization
  Requires `users:read` scope.

  ## Rate Limits
  - 100 requests per minute per API key
  - 1000 requests per hour per IP
```

**Be Specific**: Provide actionable information, not generic descriptions:

```yaml
# Good
description: Returns a paginated list of active users, ordered by creation date (newest first)

# Avoid
description: Gets users
```

**Use `examples` over `example`**: The plural `examples` field provides better SDK generation:

```yaml
# Good
examples:
  basic_user:
    value:
      id: 123
      name: "John Doe"
  admin_user:
    value:
      id: 456
      name: "Jane Admin"
      role: admin

# Avoid single example
example:
  id: 123
  name: "John Doe"
```

For more details, see [reference/examples.md](reference/examples.md).

### Reusability

**Create components for**:
- Schemas used in multiple operations
- Common parameters (pagination, filtering)
- Common responses (errors, success patterns)
- Security schemes

**Keep inline for**:
- Operation-specific request bodies
- Unique response shapes
- One-off parameters

```yaml
# Reusable schema
components:
  schemas:
    User:
      type: object
      properties:
        id: {type: integer}
        name: {type: string}

# Reference it
paths:
  /users/{id}:
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
```

For more details, see [reference/components.md](reference/components.md).

## Complex Patterns Quick Reference

These patterns are commonly challenging. Brief examples below with links to detailed guidance.

### Enums

Use string enums with clear, semantic values:

```yaml
type: string
enum:
  - pending
  - approved
  - rejected
  - cancelled
```

**Avoid**:
- Numeric strings ("0", "1", "2")
- Generic values ("value1", "value2")
- Unclear abbreviations ("pnd", "appr")

See [reference/schemas.md#enums](reference/schemas.md) for more.

### Polymorphism (oneOf/allOf/anyOf)

**oneOf**: Value matches exactly one schema (type discrimination)

```yaml
PaymentMethod:
  oneOf:
    - $ref: '#/components/schemas/CreditCard'
    - $ref: '#/components/schemas/BankAccount'
    - $ref: '#/components/schemas/PayPal'
  discriminator:
    propertyName: type
    mapping:
      credit_card: '#/components/schemas/CreditCard'
      bank_account: '#/components/schemas/BankAccount'
      paypal: '#/components/schemas/PayPal'
```

**allOf**: Value matches all schemas (composition/inheritance)

```yaml
AdminUser:
  allOf:
    - $ref: '#/components/schemas/User'
    - type: object
      properties:
        permissions:
          type: array
          items: {type: string}
```

**anyOf**: Value matches one or more schemas (flexible union)

```yaml
SearchFilter:
  anyOf:
    - $ref: '#/components/schemas/TextFilter'
    - $ref: '#/components/schemas/DateFilter'
    - $ref: '#/components/schemas/NumericFilter'
```

See [reference/schemas.md#polymorphism](reference/schemas.md) for detailed guidance.

### Discriminators

Use discriminators with `oneOf` for efficient type identification:

```yaml
Pet:
  oneOf:
    - $ref: '#/components/schemas/Dog'
    - $ref: '#/components/schemas/Cat'
  discriminator:
    propertyName: petType
    mapping:
      dog: '#/components/schemas/Dog'
      cat: '#/components/schemas/Cat'

Dog:
  type: object
  required: [petType, bark]
  properties:
    petType:
      type: string
      enum: [dog]
    bark:
      type: string

Cat:
  type: object
  required: [petType, meow]
  properties:
    petType:
      type: string
      enum: [cat]
    meow:
      type: string
```

See [reference/schemas.md#discriminators](reference/schemas.md) for more.

### Nullable Types

Handle null values differently based on OpenAPI version:

**OpenAPI 3.1** (JSON Schema 2020-12 compliant):
```yaml
type: [string, "null"]
# or
type: string
nullable: true  # Still supported for compatibility
```

**OpenAPI 3.0**:
```yaml
type: string
nullable: true
```

For optional fields, use `required` array:
```yaml
type: object
properties:
  name: {type: string}      # Can be omitted
  email: {type: string}     # Can be omitted
required: [name]            # email is optional
```

See [reference/schemas.md#nullable](reference/schemas.md) for more.

### File Uploads

Use `multipart/form-data` for file uploads:

```yaml
requestBody:
  required: true
  content:
    multipart/form-data:
      schema:
        type: object
        properties:
          file:
            type: string
            format: binary
          metadata:
            type: object
            properties:
              description: {type: string}
              tags:
                type: array
                items: {type: string}
        required: [file]
```

For base64-encoded files in JSON:
```yaml
requestBody:
  content:
    application/json:
      schema:
        type: object
        properties:
          filename: {type: string}
          content:
            type: string
            format: byte  # base64-encoded
```

See [reference/request-bodies.md#file-uploads](reference/request-bodies.md) for more.

### Server-Sent Events (SSE)

Express streaming responses with `text/event-stream`:

```yaml
responses:
  '200':
    description: Stream of events
    content:
      text/event-stream:
        schema:
          type: string
          description: |
            Server-sent events stream. Each event follows the format:

            ```
            event: message
            data: {"type": "update", "content": "..."}

            ```
        examples:
          notification_stream:
            value: |
              event: message
              data: {"type": "notification", "message": "New order received"}

              event: message
              data: {"type": "notification", "message": "Order processing complete"}
```

See [reference/responses.md#streaming](reference/responses.md) for more patterns.

## Field Reference

Detailed guidance for each major OpenAPI field:

- **Operations**: Operation IDs, methods, tags, deprecation → [reference/operations.md](reference/operations.md)
- **Schemas**: Data types, polymorphism, enums, nullable → [reference/schemas.md](reference/schemas.md)
- **Parameters**: Path, query, header, cookie parameters → [reference/parameters.md](reference/parameters.md)
- **Request Bodies**: Content types, file uploads, encoding → [reference/request-bodies.md](reference/request-bodies.md)
- **Responses**: Status codes, streaming, error patterns → [reference/responses.md](reference/responses.md)
- **Components**: Reusable definitions, organization → [reference/components.md](reference/components.md)
- **Security**: Authentication schemes, scopes, granular control → [reference/security.md](reference/security.md)
- **Examples**: Single and multiple examples, placement → [reference/examples.md](reference/examples.md)

## SDK Generation Considerations

When writing specs for SDK generation:

1. **Always define `operationId`**: Required for meaningful method names
2. **Provide rich examples**: Helps generate better documentation and tests
3. **Be explicit about required fields**: Affects SDK method signatures
4. **Use discriminators with oneOf**: Generates type-safe unions
5. **Document error responses**: Generates better error handling code

Example SDK-friendly operation:

```yaml
paths:
  /users:
    get:
      operationId: users_list
      summary: List all users
      description: Returns a paginated list of users
      parameters:
        - name: limit
          in: query
          schema: {type: integer, default: 20}
        - name: offset
          in: query
          schema: {type: integer, default: 0}
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: object
                required: [data, pagination]
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  pagination:
                    $ref: '#/components/schemas/PaginationInfo'
              examples:
                success:
                  value:
                    data: [{id: 1, name: "Alice"}, {id: 2, name: "Bob"}]
                    pagination: {total: 100, limit: 20, offset: 0}
```

This generates: `sdk.users.list({limit: 20, offset: 0})`

## Common Pitfalls

**Don't**:
- Use generic descriptions like "Gets data" or "Returns object"
- Mix naming conventions (pick one style and stick to it)
- Forget `operationId` (causes auto-generated names)
- Use `example` when you mean `examples` (plural is better)
- Make everything required (be thoughtful about optional fields)
- Inline everything (use components for reusability)
- Reference everything (inline simple one-off schemas)
- Forget to document error responses
- Use magic numbers without explanation
- Omit content types (be explicit)

**Do**:
- Provide actionable, specific descriptions
- Use consistent naming patterns throughout
- Define clear `operationId` for every operation
- Use `examples` (plural) with named examples
- Carefully consider required vs optional fields
- Balance reusability with clarity
- Document all expected responses (success and errors)
- Explain constraints and validation rules
- Be explicit about content types and formats

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [speakeasy-api](https://github.com/speakeasy-api)
- **Source:** [speakeasy-api/skills](https://github.com/speakeasy-api/skills)
- **License:** Apache-2.0
- **Homepage:** https://www.speakeasy.com/docs

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-speakeasy-api-skills-writing-openapi-specs
- Seller: https://agentstack.voostack.com/s/speakeasy-api
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
