{
	"openapi": "3.1.0",
	"info": {
		"title": "Wallabi website API",
		"version": "1.0.0",
		"summary": "Read-only JSON access to wallabi.ai articles, documentation, and services.",
		"description": "Wallabi is an AI strategy and implementation firm. This API exposes the public content of wallabi.ai as JSON for agents and integrations. It needs no authentication.\n\n**Versioning.** The version is in the URL path (`/api/v1`). Within a version, responses only gain optional fields; breaking changes ship as a new version. A retired version answers with `Deprecation` (RFC 9745) and `Sunset` (RFC 8594) headers and a `Link` to its successor for at least 180 days before it is removed. Policy: https://www.wallabi.ai/docs/developers#versioning-and-deprecation\n\n**Rate limits.** 60 requests per 60 seconds per client, enforced by each server instance: a client that stays under the advertised numbers is never limited, but the quota is not a shared global counter. Every response carries `RateLimit-Policy` and `RateLimit` headers (IETF draft-ietf-httpapi-ratelimit-headers); a 429 adds `Retry-After`.\n\n**Errors.** Every 4xx and 5xx response from the API is an `Error` object with a stable machine-readable `error.code` and a human-readable `error.message`. (A cross-site form POST is refused by the framework before it reaches the API; the API only accepts GET and HEAD.)\n\n**Not in this spec.** The homepage as Markdown (`GET https://www.wallabi.ai/` with `Accept: text/markdown`), https://www.wallabi.ai/llms.txt, https://www.wallabi.ai/sitemap.xml, and https://www.wallabi.ai/articles/rss.xml. The Wallabi platform (app.wallabi.ai) is documented at https://www.wallabi.ai/docs.",
		"contact": {
			"name": "Wallabi",
			"url": "https://www.wallabi.ai/contact",
			"email": "hello@wallabi.ai"
		}
	},
	"servers": [
		{
			"url": "https://www.wallabi.ai"
		}
	],
	"externalDocs": {
		"description": "Wallabi developer resources",
		"url": "https://www.wallabi.ai/docs/developers"
	},
	"tags": [
		{
			"name": "Meta",
			"description": "API discovery."
		},
		{
			"name": "Content",
			"description": "Articles, documentation pages, and services."
		}
	],
	"paths": {
		"/api": {
			"get": {
				"operationId": "listApiVersions",
				"security": [],
				"tags": [
					"Meta"
				],
				"summary": "List API versions",
				"description": "Returns every API version, which one is current, and each version’s deprecation status. Use it to pick a version before calling the API.",
				"responses": {
					"200": {
						"description": "API versions.",
						"headers": {
							"RateLimit-Policy": {
								"$ref": "#/components/headers/RateLimitPolicy"
							},
							"RateLimit": {
								"$ref": "#/components/headers/RateLimit"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ApiVersions"
								}
							}
						}
					},
					"405": {
						"$ref": "#/components/responses/MethodNotAllowed"
					},
					"429": {
						"$ref": "#/components/responses/RateLimited"
					},
					"500": {
						"$ref": "#/components/responses/InternalError"
					}
				}
			}
		},
		"/api/v1": {
			"get": {
				"operationId": "getApiIndex",
				"security": [],
				"tags": [
					"Meta"
				],
				"summary": "Describe the API",
				"description": "Returns the API version, endpoint URLs, the rate-limit policy, and the versioning policy. Call this first to discover the other endpoints.",
				"responses": {
					"200": {
						"description": "API description.",
						"headers": {
							"RateLimit-Policy": {
								"$ref": "#/components/headers/RateLimitPolicy"
							},
							"RateLimit": {
								"$ref": "#/components/headers/RateLimit"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ApiIndex"
								}
							}
						}
					},
					"405": {
						"$ref": "#/components/responses/MethodNotAllowed"
					},
					"429": {
						"$ref": "#/components/responses/RateLimited"
					},
					"500": {
						"$ref": "#/components/responses/InternalError"
					}
				}
			}
		},
		"/api/v1/articles": {
			"get": {
				"operationId": "listArticles",
				"security": [],
				"tags": [
					"Content"
				],
				"summary": "List Wallabi articles",
				"description": "Returns every published article on wallabi.ai, newest first, with title, description, canonical URL, dates, tags, and authors. Use it to find writing by the Wallabi team on AI strategy, data, and building with AI.",
				"responses": {
					"200": {
						"description": "Published articles.",
						"headers": {
							"RateLimit-Policy": {
								"$ref": "#/components/headers/RateLimitPolicy"
							},
							"RateLimit": {
								"$ref": "#/components/headers/RateLimit"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"required": [
										"data"
									],
									"properties": {
										"data": {
											"type": "array",
											"items": {
												"$ref": "#/components/schemas/Article"
											}
										}
									}
								}
							}
						}
					},
					"405": {
						"$ref": "#/components/responses/MethodNotAllowed"
					},
					"429": {
						"$ref": "#/components/responses/RateLimited"
					},
					"500": {
						"$ref": "#/components/responses/InternalError"
					}
				}
			}
		},
		"/api/v1/articles/{slug}": {
			"get": {
				"operationId": "getArticle",
				"security": [],
				"tags": [
					"Content"
				],
				"summary": "Get one article by slug",
				"description": "Returns one article’s metadata and canonical URL. Get valid slugs from listArticles.",
				"parameters": [
					{
						"name": "slug",
						"in": "path",
						"required": true,
						"description": "Article slug, the last path segment of its URL.",
						"schema": {
							"type": "string",
							"pattern": "^[a-z0-9-]+$"
						}
					}
				],
				"responses": {
					"200": {
						"description": "The article.",
						"headers": {
							"RateLimit-Policy": {
								"$ref": "#/components/headers/RateLimitPolicy"
							},
							"RateLimit": {
								"$ref": "#/components/headers/RateLimit"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"required": [
										"data"
									],
									"properties": {
										"data": {
											"$ref": "#/components/schemas/Article"
										}
									}
								}
							}
						}
					},
					"404": {
						"$ref": "#/components/responses/NotFound"
					},
					"405": {
						"$ref": "#/components/responses/MethodNotAllowed"
					},
					"429": {
						"$ref": "#/components/responses/RateLimited"
					},
					"500": {
						"$ref": "#/components/responses/InternalError"
					}
				}
			}
		},
		"/api/v1/docs": {
			"get": {
				"operationId": "listDocs",
				"security": [],
				"tags": [
					"Content"
				],
				"summary": "List Wallabi platform documentation pages",
				"description": "Returns every page of the Wallabi platform documentation with title, description, canonical URL, and sidebar section. Use it to find setup, concepts, and troubleshooting guides for building data apps on Wallabi.",
				"responses": {
					"200": {
						"description": "Documentation pages.",
						"headers": {
							"RateLimit-Policy": {
								"$ref": "#/components/headers/RateLimitPolicy"
							},
							"RateLimit": {
								"$ref": "#/components/headers/RateLimit"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"required": [
										"data"
									],
									"properties": {
										"data": {
											"type": "array",
											"items": {
												"$ref": "#/components/schemas/DocPage"
											}
										}
									}
								}
							}
						}
					},
					"405": {
						"$ref": "#/components/responses/MethodNotAllowed"
					},
					"429": {
						"$ref": "#/components/responses/RateLimited"
					},
					"500": {
						"$ref": "#/components/responses/InternalError"
					}
				}
			}
		},
		"/api/v1/services": {
			"get": {
				"operationId": "listServices",
				"security": [],
				"tags": [
					"Content"
				],
				"summary": "List Wallabi engagement models",
				"description": "Returns the ways to engage Wallabi, from a half-day executive workshop to an ongoing partnership, with the situation each fits, its duration, and where to start it. Prices are quoted per engagement.",
				"responses": {
					"200": {
						"description": "Engagement models.",
						"headers": {
							"RateLimit-Policy": {
								"$ref": "#/components/headers/RateLimitPolicy"
							},
							"RateLimit": {
								"$ref": "#/components/headers/RateLimit"
							}
						},
						"content": {
							"application/json": {
								"schema": {
									"type": "object",
									"required": [
										"data"
									],
									"properties": {
										"data": {
											"type": "array",
											"items": {
												"$ref": "#/components/schemas/Service"
											}
										}
									}
								}
							}
						}
					},
					"405": {
						"$ref": "#/components/responses/MethodNotAllowed"
					},
					"429": {
						"$ref": "#/components/responses/RateLimited"
					},
					"500": {
						"$ref": "#/components/responses/InternalError"
					}
				}
			}
		}
	},
	"components": {
		"schemas": {
			"ApiIndex": {
				"type": "object",
				"required": [
					"name",
					"version",
					"description",
					"links",
					"endpoints",
					"rateLimit",
					"versioning"
				],
				"properties": {
					"name": {
						"type": "string"
					},
					"version": {
						"type": "string",
						"description": "Path version, e.g. `v1`."
					},
					"description": {
						"type": "string"
					},
					"links": {
						"type": "object",
						"required": [
							"openapi",
							"documentation",
							"llmsTxt"
						],
						"properties": {
							"openapi": {
								"type": "string",
								"format": "uri"
							},
							"documentation": {
								"type": "string",
								"format": "uri"
							},
							"llmsTxt": {
								"type": "string",
								"format": "uri"
							}
						}
					},
					"endpoints": {
						"type": "object",
						"required": [
							"articles",
							"docs",
							"services"
						],
						"properties": {
							"articles": {
								"type": "string",
								"format": "uri"
							},
							"docs": {
								"type": "string",
								"format": "uri"
							},
							"services": {
								"type": "string",
								"format": "uri"
							}
						}
					},
					"rateLimit": {
						"type": "object",
						"required": [
							"quota",
							"windowSeconds",
							"headers"
						],
						"properties": {
							"quota": {
								"type": "integer",
								"description": "Requests allowed per window."
							},
							"windowSeconds": {
								"type": "integer",
								"description": "Window length in seconds."
							},
							"headers": {
								"type": "array",
								"items": {
									"type": "string"
								}
							}
						}
					},
					"versioning": {
						"type": "object",
						"required": [
							"versions",
							"policy"
						],
						"properties": {
							"versions": {
								"type": "array",
								"items": {
									"$ref": "#/components/schemas/ApiVersion"
								}
							},
							"policy": {
								"type": "string",
								"format": "uri"
							}
						}
					}
				}
			},
			"ApiVersions": {
				"type": "object",
				"required": [
					"current",
					"versions",
					"openapi"
				],
				"properties": {
					"current": {
						"type": "string",
						"description": "The version to use, e.g. `v1`."
					},
					"versions": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/ApiVersion"
						}
					},
					"openapi": {
						"type": "string",
						"format": "uri"
					}
				}
			},
			"ApiVersion": {
				"type": "object",
				"required": [
					"version",
					"status"
				],
				"properties": {
					"version": {
						"type": "string",
						"description": "e.g. `v1`."
					},
					"status": {
						"type": "string",
						"enum": [
							"current",
							"deprecated"
						]
					},
					"deprecatedAt": {
						"type": "string",
						"format": "date"
					},
					"sunsetAt": {
						"type": "string",
						"format": "date"
					},
					"successor": {
						"type": "string"
					},
					"url": {
						"type": "string",
						"format": "uri",
						"description": "The version’s base URL (in GET /api)."
					}
				}
			},
			"Article": {
				"type": "object",
				"required": [
					"slug",
					"title",
					"description",
					"url",
					"publishedAt",
					"updatedAt",
					"tags",
					"authors",
					"readingTimeMinutes"
				],
				"properties": {
					"slug": {
						"type": "string"
					},
					"title": {
						"type": "string"
					},
					"description": {
						"type": "string"
					},
					"url": {
						"type": "string",
						"format": "uri",
						"description": "Canonical page URL."
					},
					"publishedAt": {
						"type": "string",
						"description": "ISO 8601 date."
					},
					"updatedAt": {
						"type": [
							"string",
							"null"
						],
						"description": "ISO 8601 date, if revised."
					},
					"tags": {
						"type": "array",
						"items": {
							"type": "string"
						}
					},
					"authors": {
						"type": "array",
						"items": {
							"type": "string"
						}
					},
					"readingTimeMinutes": {
						"type": [
							"integer",
							"null"
						]
					}
				}
			},
			"DocPage": {
				"type": "object",
				"required": [
					"slug",
					"title",
					"description",
					"url",
					"section"
				],
				"properties": {
					"slug": {
						"type": "string",
						"description": "Path under /docs, e.g. `app/getting-started`."
					},
					"title": {
						"type": "string"
					},
					"description": {
						"type": "string"
					},
					"url": {
						"type": "string",
						"format": "uri"
					},
					"section": {
						"type": [
							"string",
							"null"
						],
						"description": "Sidebar group, or null for pages outside the sidebar."
					}
				}
			},
			"Service": {
				"type": "object",
				"required": [
					"slug",
					"title",
					"situation",
					"duration",
					"description",
					"highlights",
					"url"
				],
				"properties": {
					"slug": {
						"type": "string"
					},
					"title": {
						"type": "string"
					},
					"situation": {
						"type": "string",
						"description": "The situation this engagement fits."
					},
					"duration": {
						"type": "string"
					},
					"description": {
						"type": "string"
					},
					"highlights": {
						"type": "array",
						"items": {
							"type": "string"
						}
					},
					"url": {
						"type": "string",
						"format": "uri",
						"description": "Where to start it."
					}
				}
			},
			"Error": {
				"type": "object",
				"required": [
					"ok",
					"message",
					"error"
				],
				"properties": {
					"ok": {
						"type": "boolean",
						"const": false
					},
					"message": {
						"type": "string",
						"description": "Same as error.message."
					},
					"error": {
						"type": "object",
						"required": [
							"status",
							"code",
							"message",
							"hint",
							"docs"
						],
						"properties": {
							"status": {
								"type": "integer",
								"description": "HTTP status code."
							},
							"code": {
								"type": "string",
								"description": "Stable machine-readable error code.",
								"examples": [
									"not_found",
									"rate_limited",
									"method_not_allowed",
									"internal_error"
								]
							},
							"message": {
								"type": "string",
								"description": "Human-readable explanation."
							},
							"hint": {
								"type": "string",
								"description": "How to resolve the error."
							},
							"docs": {
								"type": "string",
								"format": "uri",
								"description": "Where to read more."
							},
							"details": {
								"description": "Optional structured detail, e.g. validation issues."
							}
						}
					}
				}
			}
		},
		"headers": {
			"RateLimitPolicy": {
				"description": "Quota policy, e.g. `\"default\";q=60;w=60` (quota q requests per w seconds).",
				"schema": {
					"type": "string"
				}
			},
			"RateLimit": {
				"description": "Current window, e.g. `\"default\";r=59;t=42` (r requests remaining, t seconds until reset).",
				"schema": {
					"type": "string"
				}
			},
			"RetryAfter": {
				"description": "Seconds to wait before retrying.",
				"schema": {
					"type": "integer"
				}
			}
		},
		"responses": {
			"MethodNotAllowed": {
				"description": "The API only accepts GET and HEAD.",
				"headers": {
					"RateLimit-Policy": {
						"$ref": "#/components/headers/RateLimitPolicy"
					},
					"RateLimit": {
						"$ref": "#/components/headers/RateLimit"
					},
					"Allow": {
						"description": "Allowed methods.",
						"schema": {
							"type": "string"
						}
					}
				},
				"content": {
					"application/json": {
						"schema": {
							"$ref": "#/components/schemas/Error"
						}
					}
				}
			},
			"NotFound": {
				"description": "No resource with that identifier.",
				"headers": {
					"RateLimit-Policy": {
						"$ref": "#/components/headers/RateLimitPolicy"
					},
					"RateLimit": {
						"$ref": "#/components/headers/RateLimit"
					}
				},
				"content": {
					"application/json": {
						"schema": {
							"$ref": "#/components/schemas/Error"
						}
					}
				}
			},
			"RateLimited": {
				"description": "Rate limit exceeded. Retry after the Retry-After interval.",
				"headers": {
					"RateLimit-Policy": {
						"$ref": "#/components/headers/RateLimitPolicy"
					},
					"RateLimit": {
						"$ref": "#/components/headers/RateLimit"
					},
					"Retry-After": {
						"$ref": "#/components/headers/RetryAfter"
					}
				},
				"content": {
					"application/json": {
						"schema": {
							"$ref": "#/components/schemas/Error"
						}
					}
				}
			},
			"InternalError": {
				"description": "Unexpected server error. Safe to retry later.",
				"headers": {
					"RateLimit-Policy": {
						"$ref": "#/components/headers/RateLimitPolicy"
					},
					"RateLimit": {
						"$ref": "#/components/headers/RateLimit"
					}
				},
				"content": {
					"application/json": {
						"schema": {
							"$ref": "#/components/schemas/Error"
						}
					}
				}
			}
		}
	}
}