{
  "openapi": "3.0.3",
  "info": {
    "title": "FabHub Public API",
    "version": "1.0.0",
    "description": "External developer API for FabHub (api.fabhub.app). Authenticate with X-API-Key using pk_test_* or pk_live_* keys. Write routes require an Idempotency-Key header."
  },
  "servers": [
    {
      "url": "https://api.fabhub.app"
    }
  ],
  "paths": {
    "/v1": {
      "get": {
        "operationId": "getCapabilities",
        "summary": "Get API capabilities",
        "description": "Returns the published capability catalog for this API version, including method, path, required OAuth-style scope, and description for each endpoint.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "API version and capability catalog",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "version",
                    "capabilities"
                  ],
                  "properties": {
                    "version": {
                      "type": "string",
                      "enum": [
                        "v1"
                      ]
                    },
                    "capabilities": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "method",
                          "path",
                          "scope",
                          "description"
                        ],
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "method": {
                            "type": "string",
                            "enum": [
                              "GET",
                              "POST",
                              "PATCH",
                              "DELETE"
                            ]
                          },
                          "path": {
                            "type": "string"
                          },
                          "scope": {
                            "type": "string"
                          },
                          "description": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        },
        "x-required-scopes": []
      }
    },
    "/v1/auth/context": {
      "get": {
        "operationId": "getAuthContext",
        "summary": "Get authenticated API key context",
        "description": "Returns the current API key scope set, environment, rate-limit tier, and tenant plan. This route is authenticated but requires no additional scope so clients can discover least-privilege capabilities without unsafe probing.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Authenticated key context",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "tenant",
                        "api_key"
                      ],
                      "properties": {
                        "tenant": {
                          "type": "object",
                          "required": [
                            "id",
                            "plan"
                          ],
                          "properties": {
                            "id": {
                              "type": "string"
                            },
                            "plan": {
                              "type": "string",
                              "enum": [
                                "free",
                                "standard",
                                "pro",
                                "enterprise"
                              ]
                            }
                          }
                        },
                        "api_key": {
                          "type": "object",
                          "required": [
                            "id",
                            "prefix",
                            "environment",
                            "scopes",
                            "rate_limit_tier",
                            "expires_at"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "prefix": {
                              "type": "string"
                            },
                            "environment": {
                              "type": "string",
                              "nullable": true,
                              "enum": [
                                "production",
                                "staging",
                                "development"
                              ]
                            },
                            "scopes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "rate_limit_tier": {
                              "type": "string"
                            },
                            "expires_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key"
          }
        },
        "x-required-scopes": []
      }
    },
    "/v1/items": {
      "get": {
        "operationId": "listItems",
        "summary": "List items",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated item list"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Insufficient scope"
          }
        },
        "x-required-scopes": [
          "items:read"
        ]
      },
      "post": {
        "operationId": "createItem",
        "summary": "Create item",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created item"
          },
          "400": {
            "description": "Invalid request body or missing idempotency key"
          },
          "403": {
            "description": "Insufficient scope"
          },
          "409": {
            "description": "Idempotency key conflict"
          }
        },
        "x-required-scopes": [
          "items:write"
        ]
      }
    },
    "/v1/items/{item_id}": {
      "get": {
        "operationId": "getItem",
        "summary": "Get item by ID",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "item_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Item details"
          },
          "404": {
            "description": "Item not found"
          }
        },
        "x-required-scopes": [
          "items:read"
        ]
      },
      "patch": {
        "operationId": "updateItem",
        "summary": "Update item by ID",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "item_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated item"
          },
          "400": {
            "description": "Invalid request body or missing idempotency key"
          },
          "404": {
            "description": "Item not found"
          },
          "409": {
            "description": "Idempotency key conflict"
          }
        },
        "x-required-scopes": [
          "items:write"
        ]
      }
    },
    "/v1/orders": {
      "get": {
        "operationId": "listOrders",
        "summary": "List orders",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "module",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "buy",
                "sell",
                "make",
                "check",
                "fix"
              ]
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "open",
                "in_progress",
                "waiting",
                "completed",
                "cancelled"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated order list"
          },
          "400": {
            "description": "Invalid query parameters"
          },
          "403": {
            "description": "Insufficient scope"
          }
        },
        "x-required-scopes": [
          "orders:read"
        ]
      }
    },
    "/v1/contacts": {
      "get": {
        "operationId": "listContacts",
        "summary": "List contacts",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contact_types",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated contact list"
          },
          "403": {
            "description": "Insufficient scope"
          }
        },
        "x-required-scopes": [
          "contacts:read"
        ]
      }
    },
    "/v1/organization": {
      "get": {
        "operationId": "getOrganization",
        "summary": "Get organization profile",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Organization profile"
          }
        },
        "x-required-scopes": [
          "organization:read"
        ]
      }
    },
    "/v1/usage": {
      "get": {
        "operationId": "getUsageSummary",
        "summary": "Get API usage summary",
        "description": "Returns tenant-scoped public API usage for the current billing period, including per-capability request counts and enforced rate-limit tier state.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Usage summary for the current billing period",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "billing_period",
                        "requests",
                        "rate_limit",
                        "api_key"
                      ],
                      "properties": {
                        "billing_period": {
                          "type": "object",
                          "properties": {
                            "start": {
                              "type": "string",
                              "format": "date"
                            },
                            "end": {
                              "type": "string",
                              "format": "date"
                            },
                            "kind": {
                              "type": "string",
                              "enum": [
                                "monthly",
                                "yearly"
                              ]
                            }
                          }
                        },
                        "requests": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer",
                              "minimum": 0
                            },
                            "by_capability": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "capability": {
                                    "type": "string"
                                  },
                                  "count": {
                                    "type": "integer",
                                    "minimum": 0
                                  }
                                }
                              }
                            }
                          }
                        },
                        "rate_limit": {
                          "type": "object",
                          "properties": {
                            "tier": {
                              "type": "string"
                            },
                            "enforcement": {
                              "type": "string",
                              "enum": [
                                "enforced"
                              ]
                            },
                            "limit_per_minute": {
                              "type": "integer",
                              "minimum": 0
                            },
                            "remaining_per_minute": {
                              "type": "integer",
                              "minimum": 0,
                              "nullable": true
                            }
                          }
                        },
                        "api_key": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "prefix": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Insufficient scope (requires usage:read)"
          }
        },
        "x-required-scopes": [
          "usage:read"
        ]
      }
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "List webhooks",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook list"
          },
          "403": {
            "description": "Insufficient scope or Enterprise plan required"
          }
        },
        "x-required-scopes": [
          "webhooks:read"
        ]
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Create webhook",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Created webhook (signing secret returned once)"
          },
          "400": {
            "description": "Invalid request body"
          },
          "403": {
            "description": "Insufficient scope or Enterprise plan required"
          }
        },
        "x-required-scopes": [
          "webhooks:write"
        ]
      }
    },
    "/v1/webhooks/{webhook_id}": {
      "get": {
        "operationId": "getWebhook",
        "summary": "Get webhook by ID",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook details"
          },
          "404": {
            "description": "Webhook not found"
          }
        },
        "x-required-scopes": [
          "webhooks:read"
        ]
      },
      "patch": {
        "operationId": "updateWebhook",
        "summary": "Update webhook by ID",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 255
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Updated webhook"
          },
          "404": {
            "description": "Webhook not found"
          }
        },
        "x-required-scopes": [
          "webhooks:write"
        ]
      },
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete webhook by ID",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Webhook deleted"
          },
          "404": {
            "description": "Webhook not found"
          }
        },
        "x-required-scopes": [
          "webhooks:delete"
        ]
      }
    },
    "/v1/webhooks/{webhook_id}/deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "summary": "List webhook delivery attempts",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "delivered",
                "failed"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated delivery log (no payload body)"
          },
          "404": {
            "description": "Webhook not found"
          }
        },
        "x-required-scopes": [
          "webhooks:deliveries:read"
        ]
      }
    },
    "/v1/audit/events": {
      "get": {
        "operationId": "listAuditEvents",
        "summary": "Export audit events (Enterprise)",
        "description": "Enterprise plan required. Requires audit:read scope. Per-key IP allowlist enforced when configured.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "action_prefix",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 120
            }
          },
          {
            "name": "actor_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "user",
                "system",
                "api"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "cursor_created_at",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "cursor_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cursor-paginated audit events"
          },
          "400": {
            "description": "Invalid query parameters"
          },
          "403": {
            "description": "Insufficient scope, Enterprise plan required, or IP not allowlisted"
          }
        },
        "x-required-scopes": [
          "audit:read"
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    }
  }
}
