Skip to content

// Guide

How to Create an OpenAPI Specification from Scratch

Build a complete Payments API definition with an operation, parameters, request and response schemas, then validate and export it.

·8 min read

Choose the behavior before writing the document

Start with one task: create a payment. POST /payments accepts a positive integer amount in minor currency units and an EUR or USD currency. Successful synchronous creation returns 201 and a payment identifier. Domain validation failures return 422 with a consistent error body.

This fictional API requires an Idempotency-Key header. The server must implement key storage and replay behavior; declaring a header in OpenAPI does not make POST safe to retry. Authentication is omitted from this teaching example. Add the actual security scheme and authorization rules before using a contract in production.

Create a complete JSON document

Save the following as openapi.json. The openapi field selects the specification version; info identifies the API and its own version. paths groups operations by URL path. components holds reusable schemas referenced using local JSON pointers.

The operation defines a required header parameter, a required JSON body and two responses. Each response has a description. The success response documents Location separately from its JSON body. Replace the illustrative server origin with a service you control before sending requests.

{
  "openapi": "3.0.3",
  "info": {
    "title": "Payments API",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.example.com"
    }
  ],
  "paths": {
    "/payments": {
      "post": {
        "operationId": "createPayment",
        "summary": "Create a payment",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePayment"
              },
              "example": {
                "amount": 4900,
                "currency": "EUR"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Payment created",
            "headers": {
              "Location": {
                "description": "URL of the created payment",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Payment"
                }
              }
            }
          },
          "422": {
            "description": "Payment validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "code": "invalid_amount",
                  "message": "Amount must be positive"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CreatePayment": {
        "type": "object",
        "required": [
          "amount",
          "currency"
        ],
        "additionalProperties": false,
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 1,
            "description": "Amount in minor currency units"
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR",
              "USD"
            ]
          }
        }
      },
      "Payment": {
        "type": "object",
        "required": [
          "id",
          "amount",
          "currency"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "amount": {
            "type": "integer",
            "minimum": 1
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR",
              "USD"
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      }
    }
  }
}

Keep parameters, bodies and response shapes distinct

Use parameters for path, query, header and cookie values. If you later add /payments/{paymentId}, declare paymentId as an in: path parameter with required: true. Use requestBody.content for payloads; requestBody.required means a body is required, while a schema's required list controls object properties.

The amount constraint rejects zero and fractional amounts. The currency enum is a deliberate two-currency example, not a claim that all payment systems support only those values. additionalProperties: false rejects unknown request fields; choose that policy deliberately because it affects forward compatibility.

Response schemas describe expected output. They do not enforce it on a running service. Keep examples valid against their schemas and use stable operationId values to help generated clients retain predictable names.

Validate the document, then test an implementation

Check JSON syntax, validate with tooling that supports OpenAPI 3.0, and resolve every local reference. Syntax checks alone do not verify operation rules or schema correctness. Review rendered documentation for missing error cases and required inputs.

A service implementing this contract should return 201 and a payment body for the request below. With amount: 0, the documented domain result is 422. These are expected outcomes, not results from a live endpoint. Contract validation cannot prove idempotency behavior, authorization or actual server responses.

POST /payments HTTP/1.1
Host: api.example.com
Content-Type: application/json
Idempotency-Key: example-payment-001

{"amount":4900,"currency":"EUR"}

Expected success:
HTTP/1.1 201 Created
Content-Type: application/json
Location: /payments/pay_123

{"id":"pay_123","amount":4900,"currency":"EUR"}

Continue in API Designer and export saved changes

REST Test can import this JSON file. Alternatively, start a manual API Source where workspace editing is permitted, create POST /payments and define its inputs and responses in API Designer. The saved normalized model feeds Documentation, Request Builder and SDK Generation.

Save pending changes before opening Settings > Export OpenAPI. Download JSON, fix validation errors and inspect export warnings before sharing it. YAML export is unavailable. The export is a saved contract snapshot, not a server implementation or a guarantee that every third-party tool supports all its keywords.

Continue in REST Test

Free browser tools

Product documentation

Related guides

// GET STARTED

Bring your API workflow into one workspace.

Start with an OpenAPI or Swagger JSON definition and keep the workflow together.

Start for free