{
  "openapi": "3.1.0",
  "info": {
    "title": "Privian Studios Content API",
    "version": "1.1.0",
    "summary": "Read-only JSON access to Privian Studios' published website content.",
    "description": "A read-only API over content that is already published on the Privian Studios website: case studies, service areas, and frequently asked questions. Every endpoint is a GET, there are no write, search, or account operations, and no authentication is required or accepted. Responses are served as JSON and mirror the pages at https://www.privianstudios.com exactly, so anything returned here can be quoted or cited as published material. Privian Studios is an AI systems and software studio based in Vancouver, British Columbia, Canada; pricing referenced in the content is in Canadian dollars (CAD).\n\n## Versioning\n\nThe version is in the URL path. `/api/v1/...` is the canonical address and is what an integration should hard-code; every response also carries an `API-Version` header naming the major it was served by.\n\nThe unversioned paths (`/api/case-studies`, `/api/services`, `/api/faq`) still work and are aliases of the current major. They follow whatever the latest major is, so a future v2 would change what they return — treat them as a convenience for one-off calls, not as a stable contract.\n\nWithin a major, changes are additive only: new fields may appear on existing objects and new endpoints may be added, and neither is treated as breaking. Removing or renaming a field, changing its type, or changing the meaning of an existing value requires a new major. Clients should ignore unknown fields rather than failing on them.\n\n## Deprecation\n\nWhen a major is retired it is announced on the endpoints themselves, not only in this document. A deprecated endpoint returns `Deprecation` (RFC 9745) with the date the deprecation took effect, `Sunset` (RFC 8594) with the date it stops responding, and a `Link` header with `rel=\"successor-version\"` pointing at the replacement. Its OpenAPI operation is also marked `deprecated: true`. There is a minimum of 180 days between the `Deprecation` date and the `Sunset` date, so an agent polling any endpoint has at least six months of warning before anything breaks. No endpoint is deprecated today.\n\n## Rate limits\n\n120 requests per 60-second window per client. Every response carries the RFC 9331 `RateLimit` and `RateLimit-Policy` fields so a caller can pace itself without having to trip the limit first; going over returns 429 with `Retry-After`. Enforcement is per server instance and therefore best-effort — see the `RateLimit` header description for what that means in practice.",
    "contact": {
      "name": "Privian Studios",
      "email": "nour@privianstudios.com",
      "url": "https://www.privianstudios.com/contact"
    },
    "license": {
      "name": "Proprietary — content © Privian Studios, quotation with attribution permitted",
      "url": "https://www.privianstudios.com"
    }
  },
  "servers": [
    {
      "url": "https://www.privianstudios.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Case studies",
      "description": "Shipped products, what each one does, and what it deliberately does not do."
    },
    {
      "name": "Services",
      "description": "The service areas Privian Studios sells, and the sub-services inside each."
    },
    {
      "name": "FAQ",
      "description": "Published questions and answers about scope, pricing, timelines, and support."
    }
  ],
  "externalDocs": {
    "description": "Plain-language summary of the same content, in llms.txt format",
    "url": "https://www.privianstudios.com/llms.txt"
  },
  "x-versioning": {
    "strategy": "uri-path",
    "current": "v1",
    "currentBasePath": "/api/v1",
    "supported": [
      "v1"
    ],
    "deprecated": [],
    "versionHeader": "API-Version",
    "unversionedAliases": {
      "basePath": "/api",
      "tracks": "latest",
      "stable": false,
      "note": "Resolves to the current major. Pin /api/v1 for anything long-lived."
    },
    "compatibility": {
      "additiveWithinMajor": true,
      "breakingChangesRequire": "new major version in the URL path",
      "clientGuidance": "Ignore unknown response fields rather than failing on them."
    },
    "deprecationPolicy": {
      "signals": [
        "Deprecation",
        "Sunset",
        "Link; rel=successor-version"
      ],
      "specs": [
        "RFC 9745",
        "RFC 8594",
        "RFC 8288"
      ],
      "minimumNoticeDays": 180,
      "alsoMarkedInSpec": "deprecated: true on the affected operation"
    }
  },
  "x-rate-limit": {
    "quota": 120,
    "windowSeconds": 60,
    "policyName": "default",
    "headers": [
      "RateLimit",
      "RateLimit-Policy",
      "Retry-After"
    ],
    "spec": "draft-ietf-httpapi-ratelimit-headers-09",
    "scope": "per client IP, per server instance",
    "enforcement": "best-effort"
  },
  "paths": {
    "/api/v1/case-studies": {
      "get": {
        "operationId": "listCaseStudies",
        "summary": "List all case studies",
        "description": "Returns every published case study in the order the website lists them, as summaries: slug, title, tagline, sector and accent colour. Use this to discover valid slugs, then call getCaseStudy for the full write-up of a single product.",
        "tags": [
          "Case studies"
        ],
        "responses": {
          "200": {
            "description": "Every published case study, as summaries.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "count",
                    "items"
                  ],
                  "properties": {
                    "count": {
                      "type": "integer",
                      "description": "Number of case studies in `items`.",
                      "examples": [
                        14
                      ]
                    },
                    "items": {
                      "type": "array",
                      "description": "Case study summaries, in website order.",
                      "items": {
                        "$ref": "#/components/schemas/CaseStudySummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/case-studies/{slug}": {
      "get": {
        "operationId": "getCaseStudy",
        "summary": "Get one case study by slug",
        "description": "Returns the full record for a single case study: overview, specification table, feature list, architecture notes, and an explicit statement of what the product does not do. Slugs come from listCaseStudies; an unknown slug returns 404.",
        "tags": [
          "Case studies"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "URL slug of the case study, exactly as returned in the `slug` field by listCaseStudies.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9][a-z0-9-]{0,63}$",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The full case study.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaseStudy"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/services": {
      "get": {
        "operationId": "listServices",
        "summary": "List service areas",
        "description": "Returns the six service areas Privian Studios sells — AI and smart systems, automation and operations, custom software, marketing and growth, content production, and foundation and support — each with its intro copy and the sub-services inside it, including the use case each sub-service is bought for.",
        "tags": [
          "Services"
        ],
        "responses": {
          "200": {
            "description": "Every service area, in website order.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "count",
                    "items"
                  ],
                  "properties": {
                    "count": {
                      "type": "integer",
                      "description": "Number of service areas in `items`.",
                      "examples": [
                        6
                      ]
                    },
                    "items": {
                      "type": "array",
                      "description": "Service areas, in website order.",
                      "items": {
                        "$ref": "#/components/schemas/Service"
                      }
                    }
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/faq": {
      "get": {
        "operationId": "listFaq",
        "summary": "List frequently asked questions",
        "description": "Returns every published question and answer, verbatim, in the order they appear on the FAQ page. Answers are written to be quoted whole; they cover engagement scope, CAD pricing bands, timelines, ownership, and post-launch support.",
        "tags": [
          "FAQ"
        ],
        "responses": {
          "200": {
            "description": "Every published question and answer.",
            "headers": {
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "API-Version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "count",
                    "items"
                  ],
                  "properties": {
                    "count": {
                      "type": "integer",
                      "description": "Number of questions in `items`.",
                      "examples": [
                        31
                      ]
                    },
                    "items": {
                      "type": "array",
                      "description": "Questions and answers, in FAQ page order.",
                      "items": {
                        "$ref": "#/components/schemas/FaqItem"
                      }
                    }
                  }
                }
              }
            }
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CaseStudySummary": {
        "type": "object",
        "title": "CaseStudySummary",
        "description": "A published case study without its long-form body — enough to list products and pick one to fetch in full.",
        "required": [
          "slug",
          "title",
          "tagline",
          "domain",
          "url",
          "image"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "description": "URL slug; pass this to getCaseStudy.",
            "examples": [
              "forgedesk"
            ]
          },
          "title": {
            "type": "string",
            "description": "Product name.",
            "examples": [
              "Forgeline"
            ]
          },
          "tagline": {
            "type": "string",
            "description": "One-sentence statement of what the product does."
          },
          "domain": {
            "type": "string",
            "description": "Sector the product was built for.",
            "examples": [
              "Manufacturing",
              "Logistics",
              "Fintech & insurance"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL of the HTML case study page — cite this, not the API path.",
            "examples": [
              "https://www.privianstudios.com/portfolio/ai-projects/forgedesk"
            ]
          },
          "image": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL of the product screenshot.",
            "examples": [
              "https://www.privianstudios.com/case-studies/forgedesk.webp"
            ]
          }
        }
      },
      "CaseStudy": {
        "type": "object",
        "title": "CaseStudy",
        "description": "The complete case study record, including the sections rendered on the case study page.",
        "required": [
          "slug",
          "title",
          "tagline",
          "domain",
          "url",
          "image",
          "overview"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "description": "URL slug of this case study.",
            "examples": [
              "forgedesk"
            ]
          },
          "title": {
            "type": "string",
            "description": "Product name."
          },
          "tagline": {
            "type": "string",
            "description": "One-sentence statement of what the product does."
          },
          "domain": {
            "type": "string",
            "description": "Sector the product was built for."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL of the HTML case study page — cite this, not the API path.",
            "examples": [
              "https://www.privianstudios.com/portfolio/ai-projects/forgedesk"
            ]
          },
          "image": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL of the product screenshot.",
            "examples": [
              "https://www.privianstudios.com/case-studies/forgedesk.webp"
            ]
          },
          "overview": {
            "type": "string",
            "description": "Long-form description of the problem and the system that solves it."
          },
          "specs": {
            "type": "array",
            "description": "Specification table: labelled facts about the build.",
            "items": {
              "type": "object",
              "required": [
                "label",
                "value"
              ],
              "properties": {
                "label": {
                  "type": "string",
                  "description": "Name of the specification.",
                  "examples": [
                    "Stack"
                  ]
                },
                "value": {
                  "type": "string",
                  "description": "Value of the specification."
                }
              }
            }
          },
          "features": {
            "type": "array",
            "description": "Numbered capability list, in display order.",
            "items": {
              "type": "object",
              "required": [
                "no",
                "text"
              ],
              "properties": {
                "no": {
                  "type": "string",
                  "description": "Zero-padded display index.",
                  "examples": [
                    "01"
                  ]
                },
                "text": {
                  "type": "string",
                  "description": "What the capability does."
                }
              }
            }
          },
          "architecture": {
            "type": "string",
            "description": "How the system is put together, in prose."
          },
          "limitations": {
            "type": "string",
            "description": "Explicit statement of what this product does not do — the stated limits, not an omission."
          }
        }
      },
      "Service": {
        "type": "object",
        "title": "Service",
        "description": "One service area and the sub-services sold inside it.",
        "required": [
          "slug",
          "title",
          "intro",
          "url",
          "services"
        ],
        "properties": {
          "no": {
            "type": "string",
            "description": "Zero-padded display index of the area.",
            "examples": [
              "01"
            ]
          },
          "slug": {
            "type": "string",
            "description": "Anchor slug on /services, e.g. /services#ai-smart-systems.",
            "examples": [
              "ai-smart-systems"
            ]
          },
          "label": {
            "type": "string",
            "description": "Short uppercase label used in the site's UI.",
            "examples": [
              "AI & SMART SYSTEMS"
            ]
          },
          "title": {
            "type": "string",
            "description": "Name of the service area.",
            "examples": [
              "AI and smart systems"
            ]
          },
          "intro": {
            "type": "string",
            "description": "What this area covers, in prose."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL of this area's section on the /services page.",
            "examples": [
              "https://www.privianstudios.com/services#ai-smart-systems"
            ]
          },
          "services": {
            "type": "array",
            "description": "Sub-services offered within this area.",
            "items": {
              "type": "object",
              "required": [
                "title",
                "description",
                "useCase"
              ],
              "properties": {
                "title": {
                  "type": "string",
                  "description": "Name of the sub-service."
                },
                "description": {
                  "type": "string",
                  "description": "What is delivered."
                },
                "useCase": {
                  "type": "string",
                  "description": "The situation a client is in when they buy this."
                },
                "url": {
                  "type": "string",
                  "format": "uri",
                  "description": "Absolute URL of a deep-dive page for this sub-service. Present only where the site's own content links one; most sub-services are documented within their service area rather than on a page of their own, so this field is absent far more often than not.",
                  "examples": [
                    "https://www.privianstudios.com/services/ai-agents"
                  ]
                }
              }
            }
          }
        }
      },
      "FaqItem": {
        "type": "object",
        "title": "FaqItem",
        "description": "A published question and its answer, verbatim from the FAQ page.",
        "required": [
          "question",
          "answer",
          "category"
        ],
        "properties": {
          "question": {
            "type": "string",
            "description": "The question, as published."
          },
          "answer": {
            "type": "string",
            "description": "The answer, as published. Safe to quote whole."
          },
          "category": {
            "type": "string",
            "description": "FAQ section this question belongs to.",
            "enum": [
              "about",
              "services",
              "pricing",
              "working",
              "canada",
              "tech"
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "Error envelope returned by every non-2xx response.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "status"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable, machine-readable error code. Match on this, not on the message.",
                "enum": [
                  "not_found",
                  "bad_request",
                  "method_not_allowed"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation of what went wrong.",
                "examples": [
                  "No case study exists with that slug."
                ]
              },
              "hint": {
                "type": "string",
                "description": "Suggested next step for the caller, when one exists — e.g. the endpoint that lists valid values.",
                "examples": [
                  "Call GET /api/v1/case-studies to list the available slugs."
                ]
              },
              "status": {
                "type": "integer",
                "description": "HTTP status code, repeated in the body for convenience.",
                "examples": [
                  404
                ]
              }
            }
          }
        }
      }
    },
    "headers": {
      "ApiVersion": {
        "description": "The API major that served this response. Constant within a major, so a client can assert on it and fail loudly if it is ever silently routed somewhere else. Useful mainly on the unversioned alias paths, where the version is otherwise invisible.",
        "schema": {
          "type": "string",
          "examples": [
            "1"
          ]
        }
      },
      "RateLimitPolicy": {
        "description": "The quota in force, as an RFC 8941 structured field: the policy name, then `q` (requests allowed) and `w` (window in seconds). Static — it describes the policy, not the caller's current standing.",
        "schema": {
          "type": "string",
          "examples": [
            "\"default\";q=120;w=60"
          ]
        }
      },
      "RateLimit": {
        "description": "The caller's current standing against that quota: `r` requests remaining, `t` seconds until the window resets. Sent on every response, not only on 429, so a client can pace itself without having to trip the limit to find out where it is.\n\nEnforcement is per server instance and best-effort. The site runs on serverless infrastructure with no shared counter, so a caller spread across instances may get more than `q` requests in a window, and a cold start resets the count. Treat these numbers as a lower bound on what you are allowed, not a guarantee of what will be refused.",
        "schema": {
          "type": "string",
          "examples": [
            "\"default\";r=119;t=60"
          ]
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying. Sent on 429 only. Takes precedence over the `t` parameter of the `RateLimit` field if the two ever disagree.",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "examples": [
            50
          ]
        }
      }
    },
    "responses": {
      "NotFound": {
        "description": "No resource exists at the requested identifier.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "unknownSlug": {
                "summary": "Unknown case study slug",
                "value": {
                  "error": {
                    "code": "not_found",
                    "message": "No case study exists with that slug.",
                    "hint": "Call GET /api/v1/case-studies to list the available slugs.",
                    "status": 404
                  }
                }
              }
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "The endpoint is read-only and accepts GET and HEAD only. Carries the same JSON error envelope as every other failure, plus an Allow header listing the permitted verbs.",
        "headers": {
          "Allow": {
            "description": "Permitted methods, comma-separated.",
            "schema": {
              "type": "string",
              "examples": [
                "GET, HEAD"
              ]
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "readOnly": {
                "value": {
                  "error": {
                    "code": "method_not_allowed",
                    "message": "This endpoint is read-only.",
                    "hint": "Use GET. The endpoints this API publishes are listed at https://www.privianstudios.com/openapi.json.",
                    "status": 405
                  }
                }
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "The quota for the current window is used up. The response body is the standard error envelope; `Retry-After` says how long to wait. Retrying earlier will simply 429 again.",
        "headers": {
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "API-Version": {
            "$ref": "#/components/headers/ApiVersion"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "overQuota": {
                "summary": "Over the published quota",
                "value": {
                  "error": {
                    "code": "rate_limited",
                    "message": "Over the limit of 120 requests per 60 seconds.",
                    "hint": "Wait 50s and retry. Enforcement is per server instance and best-effort; see https://www.privianstudios.com/openapi.json.",
                    "status": 429
                  }
                }
              }
            }
          }
        }
      },
      "BadRequest": {
        "description": "The request was malformed — currently only a syntactically invalid path parameter.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "malformedSlug": {
                "summary": "Slug is not a well-formed slug",
                "value": {
                  "error": {
                    "code": "bad_request",
                    "message": "\"Not A Slug\" is not a valid case study slug.",
                    "hint": "Slugs are lowercase letters, digits and hyphens. List them at /api/case-studies.",
                    "status": 400
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}