diff --git a/.gitignore b/.gitignore
index b675f021..2db83dd3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ librustls/cmake-build*
.idea
.venv
.vs
+/website/static/api.json
+/website/public
diff --git a/website/README.md b/website/README.md
new file mode 100644
index 00000000..7e944229
--- /dev/null
+++ b/website/README.md
@@ -0,0 +1,5 @@
+Source for the website at `https://ffi.rustls.dev/`
+
+This uses [Zola](https://www.getzola.org/).
+
+Run `zola serve` in this directory to run a local copy.
diff --git a/website/config.toml b/website/config.toml
new file mode 100644
index 00000000..6498c85b
--- /dev/null
+++ b/website/config.toml
@@ -0,0 +1,18 @@
+# The URL the site will be built for
+base_url = "https://ffi.rustls.dev/"
+
+# Whether to automatically compile all Sass files in the sass directory
+compile_sass = false
+
+# Whether to build a search index to be used later on by a JavaScript library
+build_search_index = false
+
+[markdown]
+# Whether to do syntax highlighting
+# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola
+highlight_code = true
+
+[slugify]
+paths_keep_dates = true
+
+[extra]
diff --git a/website/content/_index.md b/website/content/_index.md
new file mode 100644
index 00000000..20c3eed3
--- /dev/null
+++ b/website/content/_index.md
@@ -0,0 +1,4 @@
++++
+title = "rustls-ffi API docs"
+template = "index.html"
++++
diff --git a/website/static/GeneralSans-Variable.woff2 b/website/static/GeneralSans-Variable.woff2
new file mode 100644
index 00000000..55e906ba
Binary files /dev/null and b/website/static/GeneralSans-Variable.woff2 differ
diff --git a/website/static/rustls-ferris.png b/website/static/rustls-ferris.png
new file mode 100644
index 00000000..be48c92d
Binary files /dev/null and b/website/static/rustls-ferris.png differ
diff --git a/website/static/style.css b/website/static/style.css
new file mode 100644
index 00000000..2102f606
--- /dev/null
+++ b/website/static/style.css
@@ -0,0 +1,150 @@
+/* Base styles */
+body {
+ background-image: linear-gradient(to right top, #2f4858, #2e4e6c, #3a517f, #54528e, #744e95);
+ font-family: sans-serif;
+ margin: 0;
+}
+
+/* logo image */
+img#baby-logo {
+ filter: drop-shadow(0px 0.1em 0.2em rgba(0, 0, 0, 0.2));
+ max-height: 10%;
+ max-width: 20%;
+ display: block;
+ margin-left: 1em;
+ margin-top: 1em;
+}
+
+/* Text colors */
+* { color: white; }
+
+h1, h2, h3 { color: #c5d8ff; }
+
+.toc a { color: #c5d8ff; }
+
+/* Layout */
+.container {
+ box-sizing: border-box;
+ margin: 0 auto;
+ max-width: 1200px;
+ padding: 0 1rem;
+ width: 95%;
+}
+
+/* Typography and Links */
+h2 .header-link,
+h3 .header-link {
+ color: inherit;
+ display: block;
+ text-decoration: none;
+}
+
+.back-to-top a {
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 1em;
+ color: rgba(255, 255, 255, 0.7);
+ font-size: 0.85rem;
+ padding: 0.4em 0.8em;
+ text-decoration: none;
+ transition: all 0.2s ease;
+}
+
+.back-to-top a:hover {
+ background: rgba(255, 255, 255, 0.15);
+ color: rgba(255, 255, 255, 0.9);
+}
+
+/* Code and Pre blocks */
+p > code {
+ background: rgba(0, 0, 0, 0.1);
+ border-radius: 1em;
+ padding: 0.2em;
+}
+
+pre {
+ background: rgba(0, 0, 0, 0.1);
+ border-radius: 1em;
+ max-width: 100%;
+ padding: 1em;
+ white-space: pre-wrap;
+ margin: 1em;
+}
+
+.variant pre {
+ margin: 0.5em;
+ background-color: #2b303b;
+ color: #b48ead;
+}
+
+code {
+ word-break: break-all;
+ word-wrap: break-word;
+}
+
+/* Components */
+.toc {
+ background-image: linear-gradient(to right top, #2f4858, #2e4e6c, #3a517f, #54528e, #744e95);
+ border-radius: 1rem;
+ box-sizing: border-box;
+ filter: drop-shadow(0px 0px 1em rgba(0, 0, 0, 0.25));
+ margin: 1.5rem 0;
+ max-width: 1200px;
+ overflow-x: auto;
+ padding: 1.5rem;
+}
+
+.item {
+ background: rgba(255, 255, 255, 0.07);
+ backdrop-filter: blur(2px);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ border-radius: 1rem;
+ margin: 1.5rem 0;
+ padding: 1.5rem;
+ transition: background 0.2s ease;
+}
+
+section {
+ background-image: linear-gradient(to right top, #2f4858, #2e4e6c, #3a517f, #54528e, #744e95);
+ border-radius: 1rem;
+ filter: drop-shadow(0px 0px 1em rgba(0, 0, 0, 0.25));
+ margin: 2em 0;
+ max-width: 100%;
+ padding: 2em;
+}
+
+/* Anchors */
+h2, h3, .variant {
+ position: relative;
+}
+
+.variant a {
+ text-decoration: none;
+}
+
+h2 .anchor,
+h3 .anchor,
+.variant .anchor {
+ color: #ac66e6;
+ left: -1em;
+ opacity: 0;
+ position: absolute;
+ text-decoration: none;
+ transition: opacity 0.2s ease-in-out;
+}
+
+h2:hover .anchor,
+h3:hover .anchor,
+.variant:hover .anchor {
+ opacity: 1;
+}
+
+/* Media Queries */
+@media (max-width: 768px) {
+ .container {
+ padding: 0 0.5rem;
+ width: 100%;
+ }
+ .variant {
+ margin-left: 1rem;
+ }
+}
diff --git a/website/templates/_api_section.html b/website/templates/_api_section.html
new file mode 100644
index 00000000..c752f0a8
--- /dev/null
+++ b/website/templates/_api_section.html
@@ -0,0 +1,32 @@
+{% macro render_section(section_id, title, items) %}
+{{ variant.name }} = {{ variant.value }}
+
+