OpenAPI Specification

Fern | Fern's custom OpenAPI extensions for higher-quality SDKs

Fern supports different OpenAPI extensions so that you can generate higher-quality SDKs.

SDK method names

By default, Fern uses the tag and operationId fields to generate the SDK method. So an endpoint with a tag users and operationId users_create will generate an SDK that reads client.users.create().

To explicitly set the SDK method you can leverage the extensions:

  • x-fern-sdk-group-name which groups SDK methods together
  • x-fern-sdk-method-name which is used as the method name in the SDK

The OpenAPI below will client.users.create():

openapi.yaml
1paths:
2 /users:
3 post:
4 x-fern-sdk-group-name: users
5 x-fern-sdk-method-name: create

If you omit the x-fern-sdk-group-name extension, then the generated SDK method will live at the root. For example, the following OpenAPI will generate client.create_user():

openapi.yaml
1paths:
2 /users:
3 post:
4 x-fern-sdk-method-name: create_user

Authentication overrides

For authentication within your SDK, you may want to customize the:

  • Names of the parameters a user will populate to provide their credentials
  • Any environment variable the SDK can scan for to automatically provide the user’s credentials automatically

By default, Fern uses selects names for authentication parameters like api_key, username and password, etc. Note that this configuration is optional, and even when provided name or env may be provided alone, both are not required.

For bearer authentication:

openapi.yaml
1components:
2 securitySchemes:
3 BearerAuth:
4 type: http
5 scheme: bearer
6 x-fern-bearer:
7 name: apiKey
8 env: FERN_API_KEY

In a Python SDK for example, the above configuration would produce a client that looks like:

1import os
2
3class Client:
4
5 def __init__(self, *, apiKey: str = os.getenv("FERN_API_KEY"))

Similarly, for basic authentication the configuration is:

openapi.yaml
1components:
2 securitySchemes:
3 BasicAuth:
4 type: http
5 scheme: basic
6 x-fern-basic:
7 username:
8 name: clientId
9 env: MY_CLIENT_ID
10 password:
11 name: clientSecret
12 env: MY_CLIENT_SECRET

And for a custom auth header configuration:

openapi.yaml
1components:
2 securitySchemes:
3 ApiKeyAuth:
4 type: apiKey
5 in: header
6 name: X-API-Key
7 x-fern-header:
8 name: header
9 env: MY_HEADER_ENVVAR

Global headers

At times, your API will leverage certain headers for every endpoint, or the majority of them, we call these “global headers”. For convenience, generated Fern SDKs expose “global headers” to easily be updated on API calls. Take for example an API key, if we declare the API key as a global header, a user will be able to plug theirs in easily:

1import os
2
3class Client:
4
5 def __init__(self, *, apiKey: str)

To configure global headers, Fern will automatically pull out headers that are used in every request, or the majority of requests and mark them as global. In order to label additional headers as global, or to alias the names of global headers, you can leverage the x-fern-global-headers extension:

1x-fern-global-headers:
2 - header: custom_api_key
3 name: api_key
4 - header: userpool_id
5 optional: true

yields the following client:

1import os
2
3class Client:
4
5 def __init__(self, *, apiKey: str, userpoolId: typing.Optional[str])

Enum descriptions and names

OpenAPI doesn’t natively support adding descriptions to enum values. To do this in Fern you can use the x-fern-enum extension.

In the example below, we’ve added some descriptions to enum values. These descriptions will propagate into the generated SDK and docs website.

openapi.yml
1components:
2 schemas:
3 CardSuit:
4 enum:
5 - clubs
6 - diamonds
7 - hearts
8 - spades
9 x-fern-enum: # <----
10 clubs:
11 description: Some docs about clubs
12 spades:
13 description: Some docs about spades

x-fern-enum also supports a name field that allows you to customize the name of the enum in code. This is particularly useful when you have enums that rely on symbolic characters that would otherwise cause generated code not to compile.

For example, the following OpenAPI

openapi.yml
1components:
2 schemas:
3 Operand:
4 enum:
5 - >
6 - <
7 x-fern-enum:
8 >:
9 name: GreaterThan
10 description: Checks if value is greater than
11 <:
12 name: LessThan
13 description: Checks if value is less than

would generate

operand.ts
1export enum Operand {
2 GreaterThan = ">",
3 LessThan = "<",
4}

Schema names

OpenAPI allows you to define inlined schemas that do not have names.

openapi.yml
1components:
2 schemas:
3 Movie:
4 type: object
5 properties:
6 name:
7 type: string
8 cast:
9 type: array
10 items:
11 type: object # <---- Inline Type
12 properties:
13 firstName:
14 type: string
15 lastName:
16 type: string
17 age:
18 type: integer

Fern automatically generates names for all the inlined schemas. For example, in this example, Fern would generate the name CastItem for the inlined array item schema.

1export interface Movie {
2 name?: string;
3 cast?: CastItem[]
4}
5
6export interface CastItem { # <----- Auto-generated name
7 firstName?: string;
8 lastName?: string;
9 age?: integer;
10}

If you want to override the generated name, you can use the extension x-fern-type-name.

openapi.yml
1components:
2 schemas:
3 Movie:
4 type: object
5 properties:
6 name:
7 type: string
8 cast:
9 type: array
10 items:
11 type: object
12 x-fern-type-name: Person # <----
13 properties:
14 firstName:
15 type: string
16 lastName:
17 type: string
18 age:
19 type: integer

This would replace CastItem with Person and the generated code would read more idiomatically:

1export interface Movie {
2 name?: string;
3 cast?: Person[]
4}
5
6export interface Person { # <---- Overridden name
7 firstName?: string;
8 lastName?: string;
9 age?: integer;
10}

Parameter Names

The x-fern-parameter-name extension allows you to customize the variable name of OpenAPI parameters.

For example, if you have a header X-API-Version, you may want the parameter in code to be called version.

openapi.yml
1paths:
2 "/user":
3 get:
4 operationId: get_user
5 parameters:
6 - in: header
7 name: X-API-Version
8 x-fern-parameter-name: version # <---- specify the parameter name
9 schema:
10 type: string
11 required: true
12 ...

Server names

The x-fern-server-name extension is used to name your servers.

openapi.yml
1servers:
2- url: https://api.example.com
3 x-fern-server-name: Production
4- url: https://sandbox.example.com
5 x-fern-server-name: Sandbox

In a generated TypeScript SDK, you’d see:

environment.ts
1export const ExampleEnvironment = {
2 Production: "https://api.example.com",
3} as const;
4
5export type ExampleEnvironment = typeof ExampleEnvironment.Production;

Audiences

The x-fern-audiences extension is used to mark endpoints with a particular audience so that you can filter your SDKs and Docs as you would like in Fern.

In the example below, we have marked the POST /users endpoint with the public audience.

openapi.yml
1paths:
2 /users:
3 post:
4 x-fern-audiences: # <---- mark with the public audience
5 - public
6 operationId: users_create
7 ...
8 /projects:
9 post:
10 operationId: projects_create
11 ...

Audiences with SDKs

To filter to the public audience when generating code, update your generators.yml configuration to include:

generators.yml
1groups:
2 sdks:
3 audiences: # <---- the generators will now be filtered to the public audience
4 - public
5 generators:
6 - name: fernapi/fern-tyepscript-node-sdk
7 version: 0.8.8
8 ...

Audiences with Docs

If generating Fern Docs, you’ll need to update your docs.yml configuration to include:

docs.yml
1navigation:
2 - api: API Reference
3 audiences: # <---- the API Reference will now be filtered to the public audience
4 - public

Streaming

The x-fern-streaming extension is used to specify that an endpoint’s response is streamed. Simply include the x-fern-streaming extension within your endpoint like so:

openapi.yml
1paths:
2 /logs:
3 post:
4 x-fern-streaming: true
5 requestBody:
6 content:
7 application/json:
8 schema:
9 type: object
10 properties:
11 topic:
12 description: ""
13 type: string
14 responses:
15 "200":
16 content:
17 application/json:
18 schema:
19 $ref: "#/components/schemas/Log"
20components:
21 schemas:
22 Log:
23 type: object
24 properties:
25 topic:
26 type: string
27 message:
28 type: string

If you want to generate both a non-streaming and streaming SDK method variant for the same endpoint, you can use the extended x-fern-streaming syntax:

openapi.yml
1paths:
2 /logs:
3 post:
4 x-fern-streaming:
5 stream-condition: $request.stream
6 response:
7 $ref: "#/components/schemas/Logs"
8 response-stream:
9 $ref: "#/components/schemas/Log"
10 requestBody:
11 content:
12 application/json:
13 schema:
14 type: object
15 properties:
16 topic:
17 description: ""
18 type: string
19 responses:
20 "200":
21 content:
22 application/json:
23 schema:
24 $ref: "#/components/schemas/Log"
25components:
26 schemas:
27 Log:
28 type: object
29 properties:
30 topic:
31 type: string
32 message:
33 type: string
34 Logs:
35 type: array
36 items:
37 - $ref: "#/components/schemas/Log"

The same endpoint path (/logs in the example above) is used for both of the generated SDK methods, and the stream-condition setting includes a property in the request body that is used to distinguish whether the response should be streamed.

In the example above, a property named stream is sent as true for the streaming variant, and sent as false for the non-streaming variant. Additionally, a stream of the Log type is returned in the streaming variant, whereas the Logs array is returned in the non-streaming variant.

Ignoring Schemas or endpoints

If you want Fern to skip reading any endpoint or schemas, you can use the x-fern-ignore extension.

To skip an endpoint, you must add x-fern-ignore: true at the operation level.

openapi.yml
1paths:
2 /users:
3 get:
4 x-fern-ignore: true # <-----------
5 ...

To skip a schema, you msut add x-fern-ignore: true at the schema level.

openapi.yml
1components:
2 schemas:
3 SchemaToSkip:
4 x-fern-ignore: true # <-----------
5 ...

Overlaying extensions

Because of the number of tools that use OpenAPI, it may be more convenient to “overlay” your fern specific OpenAPI extensions onto your original definition.
In order to do this you can use the x-fern-overrides-filepath extension.

Below is an example of how someone can overlay the extensions x-fern-sdk-method-name and x-fern-sdk-group-name without polluting their original OpenAPI. The combined result is shown in the third tab.

1openapi: 3.0.0
2info:
3 title: User API
4 version: 1.0.0
5x-fern-overrides-filepath: overrides.yml # <---------
6paths:
7 /users:
8 get:
9 summary: Get a list of users
10 description: Retrieve a list of users from the system.
11 responses:
12 '200':
13 description: Successful response
14 '500':
15 description: Internal Server Error

Request + Response Examples

While OpenAPI has several fields for examples, there is no easy way to associate a request with a response. This is expecially useful when you want to show more than one example in your documentation.

x-fern-examples is an array of examples. Each element of the array can contain path-parameters, query-parameters, request and response examples values that are all associated.

openapi.yml
1paths:
2 /users/{userId}:
3 get:
4 x-fern-examples:
5 - path-parameters: # <----- Example 1
6 userId: user-1234
7 response:
8 body:
9 name: Foo
10 ssn: 1234
11 - path-parameters: # <----- Example 2
12 userId: user-4567
13 response:
14 body:
15 name: Foo
16 ssn: 4567
17components:
18 schemas:
19 User:
20 type: object
21 properties:
22 name:
23 type: string
24 ssn:
25 type: integer

Code Samples

If you’d like to specify custom code samples for your example, use code-samples.

openapi.yml
1paths:
2 /users/{userId}:
3 get:
4 x-fern-examples:
5 - path-parameters:
6 userId: user-1234
7 response:
8 body:
9 name: Foo
10 ssn: 1234
11 code-samples: # <----------- Add your code samples here
12 sdk: typescript
13 code: |
14 import { UserClient } from "...";
15
16 client.users.get("user-1234")

If you’re on the Fern Starter plan for SDKs you won’t have to worry about manually adding code samples! Our generators will do that for you.

If there’s an extension you want that doesn’t already exist, file an issue to start a discussion about it.