POST
/admin/projects/{projectId}/openapi/import
Import an OpenAPI document

document may be a parsed JSON object, or a raw YAML/JSON string (parsed with js-yaml, which accepts plain JSON text too - so no separate "format" field is needed). Import is additive only: it never modifies or removes an existing type or route, and a name/routeName/path collision with something that already exists is reported as skipped, not overwritten. It also never generates template data or creates relationships automatically - review and complete the configuration afterward via the normal Types/Relationships/Routes endpoints. Malformed documents (not an object, or not valid YAML/JSON) return 422.

Request

{
  "document": {
    "openapi": "3.0.0",
    "info": {
      "title": "Sample Blog API",
      "version": "1.0.0"
    },
    "components": {
      "schemas": {
        "User": {
          "type": "object",
          "required": [
            "id"
          ],
          "properties": {
            "id": {
              "type": "integer"
            },
            "email": {
              "type": "string",
              "format": "email"
            },
            "name": {
              "type": "string"
            }
          }
        },
        "Post": {
          "type": "object",
          "required": [
            "id"
          ],
          "properties": {
            "id": {
              "type": "integer"
            },
            "userId": {
              "type": "integer"
            },
            "title": {
              "type": "string"
            }
          }
        },
        "Widget": {
          "type": "object",
          "properties": {
            "sku": {
              "type": "string"
            },
            "name": {
              "type": "string"
            }
          }
        }
      }
    },
    "paths": {
      "/users": {
        "get": {},
        "post": {}
      },
      "/users/{id}": {
        "get": {},
        "put": {}
      },
      "/users/{id}/posts": {
        "get": {}
      },
      "/widgets": {
        "get": {}
      }
    }
  }
}

Response (200)

{
  "typesCreated": [
    "User",
    "Post"
  ],
  "typesSkipped": [
    {
      "name": "Widget",
      "reason": "no \"id\" property found to use as an unambiguous identifierField"
    }
  ],
  "routesCreated": [
    {
      "id": 1,
      "method": "GET",
      "path": "/users",
      "typeId": 1
    },
    {
      "id": 2,
      "method": "POST",
      "path": "/users",
      "typeId": 1
    },
    {
      "id": 3,
      "method": "GET",
      "path": "/users/{id}",
      "typeId": 1,
      "lookupField": "id"
    },
    {
      "id": 4,
      "method": "PUT",
      "path": "/users/{id}",
      "typeId": 1,
      "lookupField": "id"
    }
  ],
  "routesSkipped": [
    {
      "method": "GET",
      "path": "/users/{id}/posts",
      "reason": "nested route requires an existing relationship from \"Post\" to a type with routeName \"users\" - create the relationship first, then add this route manually"
    },
    {
      "method": "GET",
      "path": "/widgets",
      "reason": "no data type has routeName \"widgets\""
    }
  ]
}
Schema mapping (components.schemas → data types)
SupportedSkipped, with a reason
  • Top-level type: "object" schema with a properties map
  • string/integer/number/boolean/array/object properties, nested up to 5 levels
  • required array → per-field required
  • enum → this service's own enum (overrides format, same as a hand-authored type)
  • minimum/maximum on a numeric field → min/max
  • format: "email" and format: "uuid" → this service's own email/uuid formats (the only two with a direct, honest equivalent - see Data Types docs for the full vocabulary)
  • A top-level schema that isn't an object, or has no properties
  • A top-level or per-field $ref/oneOf/anyOf/allOf - the whole type is skipped if it's at the top level; an individual unmappable field is silently dropped instead (there's no per-field slot in the response to report it in)
  • No id property present - this importer only ever uses a literal id property as identifierField, to avoid guessing at an ambiguous one
  • A name/routeName collision with an existing (or another just-imported) data type in this Project
  • A pluralized routeName that lands on one of this deployment's reserved names (RESERVED_ROUTE_NAMES, default admin/health/payment/payments)
  • Any other OpenAPI format (e.g. date-time, int32, uri) - dropped, leaving a plain unformatted field, rather than skipping the whole type

Every created type gets a fixed generation.recordCount of 10 and no configured uniqueFields beyond its identifier - OpenAPI has no equivalent concept for either, so these are safe, adjustable defaults rather than a guess at the "right" value. routeName is derived from the schema name pluralized ("User" → "users") since OpenAPI schema names are conventionally singular while the paths that reference them are conventionally plural collections - a schema name this heuristic can't turn into a valid routeName is skipped.

Route mapping (paths → mock routes)

Each paths entry's template must already match one of the four path shapes this service supports (see Mock Routes docs) - OpenAPI's own {param} syntax is identical, so most well-formed documents need no rewriting. For each method on a path:

  • The path's own collection segment (the first segment, or the third for a nested path) is matched against a data type's routeName - no match, no route (routesSkipped).
  • A path ending in a {param} gets lookupField set to the resolved type's identifierField - the importer doesn't try to match the param's own name against a specific field.
  • A nested path (/parent/{id}/children) requires an existing relationship from the child type to a type whose routeName equals the parent segment. OpenAPI documents have no native relationship concept, so a nested path almost always gets skipped on a first import into an empty Project - this is expected, not a bug. Create the relationship (Relationships docs), then re-run the import (or add the route manually) to pick it up, exactly as demonstrated by the second/third requests in .http/admin/openapi-import.http.
  • A method/path combination that already exists as a route in this Project is reported as a collision, not duplicated.