# OpenAPI YAML Guide

Complete reference for `openapi.yaml` — the single source of truth for Redocly-rendered API documentation. The backend team uses a Python codegen script to auto-generate their schema; this guide defines every convention so the generated output matches the file exactly.

The **backend stage branch** serializers are the source of truth for schemas and field names.

## 1. Top-level file structure


```
openapi: 3.0.3
info:
  title: Core API Concepts
  version: 1.0.0
  description: '...'              ← multi-line markdown, single-quoted YAML
paths:                             ← all endpoint definitions, ordered by sidebar position
  /public/v1/.../
    post: / get:
components:
  schemas:                         ← all request/response models (alphabetical)
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
servers:
- url: https://api-dashboard.influencers.club
  description: Production
tags:                              ← tag definitions (name + optional description)
x-tagGroups:                       ← sidebar navigation groups (CRITICAL for visibility)
```

### Rules

- **OpenAPI version**: always `3.0.3`. Not 3.1.0, not 3.0.0.
- **info.description**: single-quoted YAML string with markdown. Bold headers (`**Discovery**`), paragraphs separated by blank lines. Escape literal single quotes by doubling: `can''t`.
- **servers**: one entry only — production URL. No staging/dev servers.
- **securitySchemes**: exactly `Bearer: { type: http, scheme: bearer, bearerFormat: JWT }`. No other schemes.
- **Paths ordering**: Creator Content → Enrichment → Discovery → Batch → Account (matches sidebar order).
- **Schemas ordering**: alphabetical by schema name.


## 2. POST endpoint template


```yaml
  /public/v1/creators/your/route/:
    post:
      operationId: public_v1_creators_your_route_create
      description: 'Single-line summary of what the endpoint does.


        **What you get**

        - First bullet describing the primary return value.

        - Second bullet with additional detail.


        **Credits**

        - X credits per successful request. If no data is returned, no credits
        are deducted.


        <div class="ic-ai-prompt-root" data-endpoint="your-endpoint-key"></div>'
      summary: Your Endpoint Name
      tags:
      - Your Endpoint Name
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/YourInputSchema'
          application/x-www-form-urlencoded:
            schema:
              $ref: '#/components/schemas/YourInputSchema'
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/YourInputSchema'
        required: true
      security:
      - Bearer: []
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YourResponseSchema'
          description: ''
      x-credits: X credits per successful request.
```

## 3. GET endpoint template


```yaml
  /public/v1/discovery/classifier/languages/:
    get:
      operationId: public_v1_discovery_classifier_languages_list
      description: 'Retrieve a list of supported languages...


        **Credits**

        - 0 credits. This endpoint does not deduct credits.'
      summary: Languages
      tags:
      - Dictionary
      security:
      - Bearer: []
      responses:
        '200':
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Language'
          description: ''
      x-credits: 0 credits. This endpoint does not deduct credits.
```

GET endpoints use `parameters:` instead of `requestBody:`:


```yaml
      parameters:
      - in: path
        name: batch_id
        schema:
          type: string
        description: Unique batch identifier
        required: true
      - in: query
        name: format
        schema:
          type: string
          enum:
          - csv
          - json
          default: csv
        required: false
      - in: query
        name: search
        schema:
          type: string
        description: Search string to filter results
        required: false
      - in: query
        name: offset
        schema:
          type: integer
        description: Cursor offset for pagination
        required: false
```

The `search` + `offset` pagination pattern applies to classifier endpoints: audience-brand-categories, audience-brand-names, audience-interests, audience-locations.

## 4. Path entry rules

### Trailing slash

All paths end with `/`: `/public/v1/creators/socials/` not `/public/v1/creators/socials`.

### operationId convention

- POST: `public_v1_{path_segments_with_underscores}_create`
- GET list: `public_v1_{path_segments_with_underscores}_list`
- GET single resource: `public_v1_{path_segments_with_underscores}_retrieve`
- Exception: `account_credits_usage_retrieve` (does not follow the `public_v1_` prefix)


### summary = tag name

`summary` must match the tag name exactly — it becomes the sidebar label. Multiple endpoints can share one tag (e.g. all batch endpoints use `Batch enrichment`).

### description field

- Always a single-quoted YAML string.
- Content is indented 8 spaces (relative to the `description:` key).
- Escape single quotes by doubling: `can''t` renders as `can't`.
- Opens with a summary sentence, then bold section headers.


### Required description sections

Every endpoint must have **What you get** and **Credits**. Exception: simple classifier endpoints where **Credits** alone suffices if the summary is self-explanatory.

### Optional description sections

Add only when needed: **When to use**, **How it works**, **Limits**, **Requirements**, **Output formats**, **Error Responses**, **Result format**, **Response fields**, **Supported content types per platform**, **Checking completion**, **Request**.

### HTML in descriptions

- `&nbsp;&nbsp;` for indentation in sub-bullets:

```
&nbsp;&nbsp;**Instagram** — fixed at 12 posts per page
```
- AI prompt div must be the **last thing** before the closing `'`:

```
<div class="ic-ai-prompt-root" data-endpoint="your-key"></div>'
```
The `data-endpoint` value is kebab-case and matches the `ENDPOINTS` array in `copy-for-ai.js`.


### requestBody content types

Standard POST endpoints: all three content types pointing to the same `$ref`:

- `application/json`
- `application/x-www-form-urlencoded`
- `multipart/form-data`


**Batch file upload exception**: only `multipart/form-data`.

### security

Always `- Bearer: []`.

### Authorization header parameter

Some endpoints include an explicit `in: header` Authorization parameter alongside `security: - Bearer: []`:


```yaml
      parameters:
      - in: header
        name: Authorization
        schema:
          type: string
        description: Bearer << Client JWT Token >>
        required: true
```

This applies to: Similar Creators POST, and all batch GET/POST endpoints (download, status, resume).

### x-credits

Plain-text string after `responses`. Describes credit cost. Examples:

- `0.03 credits per successful request. If no data is returned, no credits are deducted.`
- `0.01 credits per creator returned. If no creators are returned, no credits are deducted.`
- `0 credits. This endpoint does not deduct credits.`
- Multi-line for batch: `'Credits are deducted when batch records are processed successfully. Pricing follows the same credit model as the corresponding enrichment type: Handle enrichment (raw): 0.03 credits per successful record...'`


## 5. Response conventions

### 200 response (default)

Most 200 responses have `description: ''` (empty string):


```yaml
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YourResponseSchema'
          description: ''
```

### 200 with non-empty description

Two exceptions:

- Batch download: `description: CSV or JSON file with enriched results depending on the \`format` query parameter.`
- Account credits: `description: OK`


### Error responses

Use `type: object` with `additionalProperties: {}`:


```yaml
        '400':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: Bad Request - Invalid input
        '403':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: Forbidden - Insufficient permissions
        '404':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: Not Found - Invalid batch ID
        '429':
          content:
            application/json:
              schema:
                type: object
                additionalProperties: {}
          description: Too Many Requests - Rate limit exceeded
```

Currently used on: batch create (400/403/429), batch download (400/404), batch resume (400/403/404), batch status (404).

### Binary/file download response

Batch download returns two content types:


```yaml
        '200':
          description: CSV or JSON file with enriched results...
          content:
            text/csv:
              schema:
                type: string
                format: binary
                description: Default CSV file with enriched results
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    input_value:
                      type: string
                    status:
                      type: string
                    enrichment_data:
                      type: object
                      additionalProperties: true
```

### Response wrapper pattern

Most endpoints wrap their result in a standard envelope:


```yaml
    SomeResponse:
      type: object
      properties:
        credits_cost:
          type: number
          nullable: true
        result:
          $ref: '#/components/schemas/SomeResult'
      required:
      - result
```

Variations:

- `AudienceOverlapResponse` adds `credits_left`, `status`, `success`, and uses `basics`/`details` instead of `result`.
- `DiscoveryAPIResponse` uses `credits_left` (format: decimal), `total`, `limit`, `accounts` (array).
- `response_meta` is internal and not documented.


### Inline response schemas

The account credits endpoint defines its response schema inline in the path, not via `$ref`:


```yaml
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  credits_available:
                    type: number
                  credits_used:
                    type: number
                required:
                - credits_available
                - credits_used
              example:
                credits_available: 0
                credits_used: 0
```

A named schema `AccountCreditsUsageResponse` also exists in components but is not referenced by the path.

## 6. Schema conventions

### DRF serializer → OpenAPI mapping

| Backend (Django REST Framework) | OpenAPI 3.0.3 |
|  --- | --- |
| `serializers.CharField()` | `type: string` |
| `serializers.CharField(required=True)` | `type: string`, `minLength: 1` + in `required` array |
| `serializers.CharField(allow_blank=True)` | `type: string` (no `minLength`) |
| `serializers.IntegerField()` | `type: integer` |
| `serializers.FloatField()` | `type: number`, `format: double` |
| `serializers.DecimalField(max_digits=N, decimal_places=M)` | `type: string`, `format: decimal`, `pattern: ^-?\d{0,N}(?:\.\d{0,M})?$` |
| `serializers.BooleanField()` | `type: boolean` |
| `serializers.DateTimeField()` | `type: string`, `format: date-time` |
| `serializers.URLField()` | `type: string`, `format: uri` |
| `serializers.EmailField()` | `type: string`, `format: email` |
| `serializers.FileField()` | `type: string`, `format: binary` |
| `serializers.DictField()` | `type: object`, `additionalProperties: {}` |
| `serializers.ListField(child=CharField())` | `type: array`, `items: { type: string }` |
| `serializers.ListField()` (no child) | `type: array`, `items: {}` |
| `serializers.ChoiceField(choices=[...])` | `type: string`, `enum: [...]` |
| `allow_null=True` | Add `nullable: true` |
| `required=False` | Do NOT include in `required` array |
| `required=True` (or default) | Include in `required` array |
| `default=value` | Add `default: value` |
| `min_length=N` on `ListField` | `minItems: N` |
| `max_length=N` on `ListField` | `maxItems: N` |
| `min_length=1` on `CharField` | `minLength: 1` |
| `help_text="..."` | `description: '...'` |
| Nested `SomeSerializer()` | `$ref: '#/components/schemas/Some'` |
| Nested `SomeSerializer(many=True)` | `type: array`, `items: { $ref: '#/components/schemas/Some' }` |
| Nested with `allow_null=True` | `allOf: [{ $ref }]` + `nullable: true` (see composition patterns) |


### Pydantic BaseModel → OpenAPI mapping

| Pydantic | OpenAPI |
|  --- | --- |
| `Field(default=None)` | `nullable: true`, not in `required` |
| `Optional[str]` | `type: string`, `nullable: true` |
| `Optional[int]` | `type: integer`, `nullable: true` |
| `Optional[bool]` | `type: boolean`, `nullable: true` |
| `Optional[list[str]]` | `type: array`, `items: { type: string }`, `nullable: true` |
| `validation_alias=AliasChoices(...)` | Use the **first** alias as the field name |


### Field names

Use the **exact** field names from the backend serializer. If the serializer uses `snake_case`, the schema uses `snake_case`. If it uses `camelCase`, the schema uses `camelCase`. **Do not rename fields.**

Platform-native field names are kept as-is:

- Twitch: `profileImageURL`, `displayName`, `isPartner`, `panelsUrls`
- Twitter: `userid` (no underscore)
- ConnectedSocial: `userId`, `fullname`


### Identifier field descriptions

Any field that acts as an identifier (`handle`, `creators`, `email`, `post_id`, etc.) **must** have a `description` explaining exactly what values are accepted. Check the backend view to see how the field is processed.


```yaml
        handle:
          type: string
          description: 'Creator identifier — username, profile URL, or YouTube channel ID (UC...).'
```

Never leave identifier fields with just `type: string` and no description.

### Schema suffix naming conventions

| Suffix | When to use | Example |
|  --- | --- | --- |
| `Request` | Input schemas for request bodies | `AudienceFilterRequest`, `SortRequest` |
| `RequestRequest` | DRF auto-gen when serializer name already ends in `Request` | `EnrichFullHandleRequestRequest` |
| `Response` | Top-level response wrappers | `HandleFullPlatformResponse` |
| `Result` | Inner result data object | `CreatorPostsResult`, `HandleFullResultResponse` |
| `Query` | Input for query-style POST endpoints | `CreatorPostsQuery`, `ConnectedSocialsQuery` |
| `Input` | Input for specific action endpoints | `AudienceOverlapInput` |
| `Model` | Sub-schema representing a data entity | `PostItemModel`, `EngagementModel` |
| `DocsRequest` | Platform-specific variant for documentation display | `DiscoveryAPISearchInputInstagramDocsRequest` |
| (none) | Utility/reference schemas | `Brand`, `Language`, `YouTubeTopic` |


### Duplicate *RequestRequest naming

When a backend serializer is named `SomeRequestSerializer`, DRF auto-generation appends `Request`, producing `SomeRequestRequest`. This is expected behavior. Keep these names as-is to match backend codegen output. Current examples: `EnrichFullHandleRequestRequest`, `EnrichRawHandleRequestRequest`, `EnrichBasicEmailRequestRequest`, `EnrichAdvancedEmailRequestRequest`, `EnrichSimilarCreatorsRequestRequest`, `DiscoveryAPISearchInputRequestRequest`.

## 7. Schema composition patterns

### Simple $ref

For non-nullable required nested objects:


```yaml
        user:
          $ref: '#/components/schemas/OverlapUser'
```

### allOf + $ref + nullable

OAS 3.0.3 cannot put `nullable: true` on a `$ref` directly. Use `allOf`:


```yaml
        gender:
          allOf:
          - $ref: '#/components/schemas/AudienceGenderFilterRequest'
          nullable: true
```

### allOf + $ref + description

Adding a description alongside a `$ref`:


```yaml
        creator_has:
          allOf:
          - $ref: '#/components/schemas/CreatorHasRequest'
          description: 'Supported platforms: Instagram, YouTube, TikTok, Twitch, Twitter, OnlyFans'
```

### allOf + $ref + nullable + default + description

The most complex form, combining all modifiers:


```yaml
        subscriber_growth:
          allOf:
          - $ref: '#/components/schemas/GrowthRequest'
          nullable: true
          default:
            growth_percentage: null
            time_range_months: 3
          description: 'Supported platforms: YouTube'
```

### oneOf + discriminator (platform switching)

Used for the Discovery API to show platform-specific request bodies:


```yaml
    DiscoveryAPISearchInputRequestRequest:
      oneOf:
      - $ref: '#/components/schemas/DiscoveryAPISearchInputInstagramDocsRequest'
      - $ref: '#/components/schemas/DiscoveryAPISearchInputYouTubeDocsRequest'
      - $ref: '#/components/schemas/DiscoveryAPISearchInputTikTokDocsRequest'
      - $ref: '#/components/schemas/DiscoveryAPISearchInputTwitchDocsRequest'
      - $ref: '#/components/schemas/DiscoveryAPISearchInputTwitterDocsRequest'
      - $ref: '#/components/schemas/DiscoveryAPISearchInputOnlyFansDocsRequest'
      discriminator:
        propertyName: platform
        mapping:
          instagram: '#/components/schemas/DiscoveryAPISearchInputInstagramDocsRequest'
          youtube: '#/components/schemas/DiscoveryAPISearchInputYouTubeDocsRequest'
          tiktok: '#/components/schemas/DiscoveryAPISearchInputTikTokDocsRequest'
          twitch: '#/components/schemas/DiscoveryAPISearchInputTwitchDocsRequest'
          twitter: '#/components/schemas/DiscoveryAPISearchInputTwitterDocsRequest'
          onlyfans: '#/components/schemas/DiscoveryAPISearchInputOnlyFansDocsRequest'
```

Each platform `DocsRequest` constrains `platform` to a single-value enum and references platform-specific filters. The description on each explains the pattern:


```
Redocly can''t dynamically show/hide fields based on `platform`, so we expose
a `oneOf` discriminator in the view schema that points to per-platform request variants.
```

### oneOf for union types

Fields accepting either string or integer:


```yaml
        last_post:
          oneOf:
          - type: string
          - type: integer
          nullable: true
          description: 'Supported platforms: Instagram, TikTok, Twitter'
```

### additionalProperties

| Pattern | When to use | Example |
|  --- | --- | --- |
| `additionalProperties: {}` | Flexible object, any key-value (DictField, error responses) | Error schemas, `metadata`, `income_data` |
| `additionalProperties: true` | Explicit open-ended object | `AudienceDataResponse`, batch `enrichment_data` |
| `additionalProperties: { type: string }` | Object with string values only | `CreatorContentDetailsResponse.result` |


## 8. Format specifiers

| Format | Type | When to use | Example fields |
|  --- | --- | --- | --- |
| `format: double` | `type: number` | Float/engagement percentages, rates, min/max ranges | `engagement_percent`, `credits_cost`, `min`, `max` |
| `format: decimal` | `type: string` | Python Decimal fields (serializes to string) | `credits_left`, `credits_used` |
| `format: date-time` | `type: string` | Timestamp fields | `created_at`, `started_at`, `estimated_completion` |
| `format: uri` | `type: string` | URL fields | `picture`, `profile_pic_url`, `links_in_bio` items |
| `format: email` | `type: string` | Email fields | `email` in enrichment responses |
| `format: binary` | `type: string` | File upload/download | `file` in batch input, CSV download response |


**Important**: `format: decimal` always pairs with a `pattern` regex and `type: string` (not `type: number`):


```yaml
        credits_left:
          type: string
          format: decimal
          pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
```

## 9. Field constraints

### minLength: 1

Applied to required `CharField` fields that cannot be empty:


```yaml
        handle:
          type: string
          minLength: 1
```

### minItems / maxItems

Applied to list fields:


```yaml
        creators:
          type: array
          items:
            type: string
          minItems: 2
          maxItems: 10
```

### nullable: true

Only add if the backend serializer explicitly has `allow_null=True` or Pydantic uses `Optional[...]`. Do not add by default.

### default values

Must match the backend exactly:

- Boolean: `default: false` (e.g. `has_videos`, `exclude_role_based_emails`, `include_lookalikes`)
- Numeric: `default: 1` (e.g. `min_pct` on audience filters), `default: 3` (`time_range_months`)
- String: `default: csv`, `default: relevancy`, `default: desc`, `default: ok`, `default: preferred`
- Object: `default: { growth_percentage: null, time_range_months: 3 }`


### pattern (regex)

For decimal-formatted string fields:


```yaml
        credits_used:
          type: string
          format: decimal
          pattern: ^-?\d{0,8}(?:\.\d{0,2})?$
```

### enum

Lowercase values. Can be on the property directly or on array items:


```yaml
        platform:
          enum:
          - instagram
          - youtube
          - tiktok
          type: string
```


```yaml
        exclude_platforms:
          type: array
          items:
            enum:
            - instagram
            - youtube
            - tiktok
            type: string
```

## 10. Redocly extensions

### x-expandLevel: 2

Applied to response schemas with deeply nested `$ref` structures. Tells Redocly to auto-expand 2 levels deep in the response panel. Place directly under the schema name:


```yaml
    BatchEnrichmentResponse:
      type: object
      x-expandLevel: 2
      properties:
        ...
```

**Schemas that have x-expandLevel: 2:**

- `BatchEnrichmentResponse`
- `BatchEnrichmentStatusResponse`
- `CreatorContentDetailsResponse`
- `HandleEmailPlatformResponse`
- `HandleEmailResponse`
- `HandleFullPlatformResponse`
- `HandleFullResultResponse`
- `HandleRawPlatformResponse`
- `HandleRawResultResponse`
- `AccountCreditsUsageResponse`


### x-credits

See section 4 (path entry rules).

### x-tagGroups

See section 13 (x-tagGroups rules).

## 11. Special patterns

### Platform-specific response schemas

Three tiers of per-platform schemas:

- `HandleRaw[Platform]Response` — 12 platforms: Instagram, YouTube, TikTok, OnlyFans, Twitter, Twitch, Facebook, Pinterest, Reddit, Snapchat, Discord, LinkedIn. Collected via `HandleRawResultResponse`.
- `HandleFull[Platform]Response` — 7 platforms: Instagram, YouTube, TikTok, OnlyFans, Twitter, Twitch, LinkedIn. Collected via `HandleFullResultResponse`.
- `HandleEmail[Platform]Response` — 6 platforms: Instagram, YouTube, TikTok, OnlyFans, Twitter, Twitch. Collected via `HandleEmailPlatformResponse`.


Each platform response has platform-native field names. Twitch uses camelCase (`profileImageURL`, `displayName`). Do not rename.

### CreatorHasRequest pattern

Large schema with 50+ boolean fields for platform/service presence filtering:


```yaml
        has_amazonaffiliates:
          type: boolean
          nullable: true
          default: false
```

All fields follow `has_[platform_or_service]` naming. Schema ends with `example: {}`.

### Discovery filters nesting

`AudienceFilterRequest` is `nullable: true` and contains sub-schemas:

- `location` — array of `AudienceLocationFilterRequest`
- `gender` — `allOf: [AudienceGenderFilterRequest]`, nullable
- `language` — array of `AudienceLanguageFilterRequest`
- `age` — array of `AudienceAgeFilterRequest`
- `interests` — array of `AudienceInterestFilterRequest`
- Plus simple fields: `brands`, `brand_categories`, `credibility` (enum)


Each sub-filter has `min_pct` with `default: 1`.

### Batch multi-endpoint workflow

Four related endpoints under the `Batch enrichment` tag:

1. `POST /public/v1/enrichment/batch/` — create (multipart only, error responses 400/403/429)
2. `GET /public/v1/enrichment/batch/{batch_id}/status/` — check status
3. `POST /public/v1/enrichment/batch/{batch_id}/resume/` — resume paused job
4. `GET /public/v1/enrichment/batch/{batch_id}/` — download results (CSV/JSON)


### Empty items: {} for untyped arrays

Raw platform data arrays where item type is unconstrained:


```yaml
        post_data:
          type: array
          items: {}
```

### example field on schemas

Used sparingly on request and response schemas:


```yaml
    EnrichFullHandleRequestRequest:
      type: object
      ...
      example:
        handle: cristiano
        platform: instagram
        email_required: preferred
        include_lookalikes: false
        include_audience_data: false
```

Also used on `CreatorHasRequest` (`example: {}`) and inline response schemas (account credits).

## 12. Result schemas — use named, expanded models

Every endpoint should have a dedicated **named Result schema** (e.g. `CreatorPostsResult`, `AudienceOverlapResponse`) rather than inlining the response structure. Break nested objects into their own schemas so Redocly renders them as expandable sections.

**Good** — each nested object is its own schema:


```yaml
    CreatorPostsResult:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/PostItemModel'
        num_results:
          type: integer

    PostItemModel:
      type: object
      properties:
        pk:
          type: string
        engagement:
          $ref: '#/components/schemas/EngagementModel'
        user:
          $ref: '#/components/schemas/UserInfoModel'
```

**Bad** — everything inlined in one flat schema:


```yaml
    CreatorPostsResult:
      type: object
      properties:
        items:
          type: array
          items:
            type: object
            properties:
              pk:
                type: string
              likes:
                type: integer
```

Follow the backend serializer structure — if the backend has a nested serializer, create a corresponding named schema.

## 13. Parameter descriptions and examples (future iteration)

> **Note**: This is planned for a future iteration. When updating or adding endpoints, start applying this pattern where practical.


Every request parameter should include:

- A `description` explaining what the field does and what values are accepted.
- An `example` value showing a realistic input.



```yaml
        handle:
          type: string
          description: 'Creator identifier — username, profile URL, or YouTube channel ID (UC...).'
          example: 'cristiano'
        platform:
          type: string
          enum:
          - instagram
          - tiktok
          - youtube
          description: 'Social platform to query.'
          example: 'instagram'
```

## 14. Tags section

Located after `servers:`, before `x-tagGroups`. Each endpoint's tag must be listed here.


```yaml
tags:
- name: Discovery API
- name: Similar Creators
- name: Audience Overlap
- name: Dictionary
  description: |
    Provides the complete set of supported filter values used by the Discovery
    and Similar Creators endpoints...
- name: Connected Socials
- name: Enrich by handle full
- name: Enrich by handle raw
- name: Enrich by email advanced
- name: Enrich by email basic
- name: Batch enrichment
  description: 'Bulk enrichment jobs: create, check status, resume, and download results.'
- name: Creator Posts
- name: Post Details
- name: Account credits & usage
  description: Account-level endpoints (credits, usage).
```

**Rules:**

- Tags without descriptions: `- name: TagName`
- Multi-line descriptions: pipe literal `|` block scalar
- Single-line descriptions: single-quoted string
- Add `description:` only when the tag name alone isn't self-explanatory


## 15. x-tagGroups (sidebar navigation)

Located at the very bottom of the file. **Every tag must be listed here or it will not appear in the sidebar.**


```yaml
x-tagGroups:
- name: Creator Discovery
  tags:
  - Discovery API
  - Similar Creators
  - Audience Overlap
  - Dictionary
- name: Creator Enrichment
  tags:
  - Connected Socials
  - Enrich by handle full
  - Enrich by handle raw
  - Enrich by email advanced
  - Enrich by email basic
  - Batch enrichment
- name: Creator Content
  tags:
  - Creator Posts
  - Post Details
- name: User Info
  tags:
  - Account credits & usage
```

To add a new endpoint: add its tag under the correct existing group. To create a new group: check with the team first — do not invent groups.

## 16. Common mistakes

1. **Endpoint not in sidebar** — tag missing from `x-tagGroups`.
2. **Tag undeclared** — tag in `x-tagGroups` but missing from `tags:` section.
3. **Schema field mismatch** — field name doesn't match backend serializer (e.g. `camelCase` vs `snake_case`).
4. **Invented constraints** — do not add `enum`, `minItems`, `maxItems`, `description`, or any constraint not in the backend serializer.
5. **Missing content types** — POST `requestBody` must have all 3 (except batch: multipart only).
6. **Broken YAML string** — unescaped single quote in single-quoted description. Use `''`.
7. **Missing trailing slash** — paths must end with `/`.
8. **Wrong operationId** — must follow `public_v1_{path}_{method}` convention.
9. **Forgetting AI prompt div** — `<div class="ic-ai-prompt-root" data-endpoint="..."></div>` at end of every description.
10. **Nullable without allow_null** — only add `nullable: true` if backend has `allow_null=True`.
11. **$ref with nullable directly** — OAS 3.0.3 cannot put `nullable` on a `$ref`. Must use `allOf: [{ $ref }]` with `nullable: true`.
12. **Wrong format for DecimalField** — must be `type: string, format: decimal`, NOT `type: number`.
13. **Missing minLength: 1** — required CharField fields should have `minLength: 1`.
14. **Missing x-expandLevel: 2** — response schemas with nested `$ref` should have this for proper Redocly rendering.
15. **Renaming platform-native fields** — camelCase fields like `profileImageURL`, `displayName`, `isPartner`, `userId` must be kept as-is.