{
  "openapi": "3.1.0",
  "info": {
    "title": "Somalynk public read API",
    "version": "1.1.0",
    "summary": "Read-only access to published content on somalynk.com.",
    "description": "Somalynk has no bespoke product API. This document describes the subset of the standard WordPress REST API that somalynk.com actually exposes anonymously, so that an agent can read published content without scraping HTML.\n\nEverything here is read-only, unauthenticated, and was probed live against https://somalynk.com on 2026-08-29 before being written down. Endpoints that exist in WordPress but are not listed here are either authenticated, not useful, or not guaranteed to stay available.\n\nThe clinic booking system, the patient portal and the practice software live on separate hosts and are deliberately NOT part of this API. Do not infer endpoints for them from this document.\n\nContent returned by this API is general information. Clinical text is not individual medical advice and must not be presented to a user as a diagnosis or a treatment instruction.\n\nRate limits. The JSON endpoints (/wp-json and /api) are limited to 240 requests per 60 seconds per client address, and every JSON response carries RateLimit-Policy and RateLimit so a client can pace itself without guessing. Exceeding the quota returns 429 with Retry-After and the same error envelope as every other failure. HTML pages are not rate limited. Authenticated requests are exempt. Cloudflare sits in front of the origin and may apply its own protections independently of this limit.",
    "contact": {
      "name": "Somalynk",
      "url": "https://somalynk.com/contact/"
    }
  },
  "servers": [
    {
      "url": "https://somalynk.com",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "llms.txt, a curated plain-text index of this site for agents",
    "url": "https://somalynk.com/llms.txt"
  },
  "tags": [
    {
      "name": "discovery",
      "description": "API index and capability discovery."
    },
    {
      "name": "content",
      "description": "Published posts and pages."
    },
    {
      "name": "search",
      "description": "Cross-type search over published content."
    }
  ],
  "paths": {
    "/wp-json/": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getApiIndex",
        "summary": "API index",
        "description": "Site metadata and the list of registered REST namespaces and routes. Useful for discovery; most namespaces it lists are plugin-internal or authenticated and are not part of the supported surface described here.",
        "responses": {
          "200": {
            "description": "The API index.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimitState"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/wp-json/wp/v2/posts": {
      "get": {
        "tags": [
          "content"
        ],
        "operationId": "listPosts",
        "summary": "List published posts",
        "description": "Returns published blog posts, newest first by default. 50 posts were published at the time of writing. Pagination metadata is returned in the X-WP-Total, X-WP-TotalPages and Link response headers.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/perPage"
          },
          {
            "$ref": "#/components/parameters/search"
          },
          {
            "$ref": "#/components/parameters/slug"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "$ref": "#/components/parameters/order"
          },
          {
            "$ref": "#/components/parameters/orderbyPost"
          },
          {
            "$ref": "#/components/parameters/fields"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of posts.",
            "headers": {
              "X-WP-Total": {
                "$ref": "#/components/headers/XWPTotal"
              },
              "X-WP-TotalPages": {
                "$ref": "#/components/headers/XWPTotalPages"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimitState"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Post"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidParam"
          },
          "401": {
            "$ref": "#/components/responses/ForbiddenContext"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/wp-json/wp/v2/posts/{id}": {
      "get": {
        "tags": [
          "content"
        ],
        "operationId": "getPost",
        "summary": "Retrieve one post by id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The post id.",
            "schema": {
              "type": "integer"
            },
            "example": 993
          },
          {
            "$ref": "#/components/parameters/fields"
          }
        ],
        "responses": {
          "200": {
            "description": "The post.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimitState"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidParam"
          },
          "401": {
            "$ref": "#/components/responses/ForbiddenContext"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/wp-json/wp/v2/pages/{id}": {
      "get": {
        "tags": [
          "content"
        ],
        "operationId": "getPage",
        "summary": "Retrieve one page by id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The page id.",
            "schema": {
              "type": "integer"
            },
            "example": 1052
          },
          {
            "$ref": "#/components/parameters/fields"
          }
        ],
        "responses": {
          "200": {
            "description": "The page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Page"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimitState"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidParam"
          },
          "401": {
            "$ref": "#/components/responses/ForbiddenContext"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/wp-json/wp/v2/pages": {
      "get": {
        "tags": [
          "content"
        ],
        "operationId": "listPages",
        "summary": "List published pages",
        "description": "Returns published pages. This includes the translated twins created by Polylang, so the same page appears once per language with its own id and link. 129 pages were published at the time of writing.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/perPage"
          },
          {
            "$ref": "#/components/parameters/search"
          },
          {
            "$ref": "#/components/parameters/slug"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "$ref": "#/components/parameters/order"
          },
          {
            "$ref": "#/components/parameters/orderbyPost"
          },
          {
            "$ref": "#/components/parameters/parent"
          },
          {
            "$ref": "#/components/parameters/fields"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of pages.",
            "headers": {
              "X-WP-Total": {
                "$ref": "#/components/headers/XWPTotal"
              },
              "X-WP-TotalPages": {
                "$ref": "#/components/headers/XWPTotalPages"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimitState"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Page"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidParam"
          },
          "401": {
            "$ref": "#/components/responses/ForbiddenContext"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/wp-json/wp/v2/search": {
      "get": {
        "tags": [
          "search"
        ],
        "operationId": "search",
        "summary": "Search published content",
        "description": "Lightweight cross-type search. Returns a compact record per hit (id, title, url, type, subtype) rather than the full object; follow the url, or the _links.self href, for the full record. The search parameter is optional: omitting it returns unfiltered results.",
        "parameters": [
          {
            "$ref": "#/components/parameters/searchQuery"
          },
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/perPage"
          },
          {
            "$ref": "#/components/parameters/searchType"
          },
          {
            "$ref": "#/components/parameters/searchSubtype"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of search results.",
            "headers": {
              "X-WP-Total": {
                "$ref": "#/components/headers/XWPTotal"
              },
              "X-WP-TotalPages": {
                "$ref": "#/components/headers/XWPTotalPages"
              },
              "Link": {
                "$ref": "#/components/headers/Link"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimitState"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SearchResult"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidParam"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getApiDiscovery",
        "summary": "API discovery document",
        "description": "Names every machine-readable description this site publishes: the OpenAPI document, the wp-json base, the developer page and llms.txt. It answers application/json to every client, including a browser, and never redirects to HTML. Start here if you have only the domain name.",
        "responses": {
          "200": {
            "description": "The discovery document.",
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimitState"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Link": {
                "description": "Points at the OpenAPI document: rel=\"describedby\".",
                "schema": {
                  "type": "string",
                  "examples": [
                    "<https://somalynk.com/openapi.json>; rel=\"describedby\"; type=\"application/json\""
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiDiscovery"
                },
                "example": {
                  "name": "Somalynk",
                  "description": "Read-only public WordPress REST API for the published content of somalynk.com. There is no product API, no booking endpoint and no patient data.",
                  "openapi": "https://somalynk.com/openapi.json",
                  "api_base": "https://somalynk.com/wp-json/",
                  "documentation": "https://somalynk.com/developers/",
                  "llms_txt": "https://somalynk.com/llms.txt"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/{path}": {
      "get": {
        "tags": [
          "discovery"
        ],
        "operationId": "getApiSubpath",
        "summary": "Any path below /api",
        "description": "Nothing is published under /api besides the discovery document itself. Every subpath returns a JSON 404 in the same error envelope as the rest of this API, never an HTML page, so a client that guessed a path gets a parseable answer and a pointer to the real documentation.",
        "parameters": [
          {
            "name": "path",
            "in": "path",
            "required": true,
            "description": "Any path segment or segments below /api.",
            "schema": {
              "type": "string",
              "examples": [
                "v1/appointments"
              ]
            }
          }
        ],
        "responses": {
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "XWPTotal": {
        "description": "Total number of records matching the query, across all pages.",
        "schema": {
          "type": "integer"
        },
        "example": 50
      },
      "XWPTotalPages": {
        "description": "Total number of pages available at the current per_page.",
        "schema": {
          "type": "integer"
        },
        "example": 50
      },
      "Link": {
        "description": "RFC 8288 pagination links, with rel=\"next\" and rel=\"prev\" where applicable.",
        "schema": {
          "type": "string"
        },
        "example": "<https://somalynk.com/wp-json/wp/v2/posts?per_page=1&page=2>; rel=\"next\""
      },
      "RateLimitPolicy": {
        "description": "The rate-limit policy in force, as a structured field (draft-ietf-httpapi-ratelimit-headers). Format: \"<policy name>\";q=<quota>;w=<window in seconds>. Always sent on the JSON endpoints, even when the live counter is unavailable.",
        "schema": {
          "type": "string",
          "examples": [
            "\"somalynk-json\";q=240;w=60"
          ]
        }
      },
      "RateLimitState": {
        "description": "Live state of the quota for this client, as a structured field. Format: \"<policy name>\";r=<requests remaining>;t=<seconds until the window resets. Omitted, deliberately, if the server could not read its counter: an absent header means unknown, never zero.",
        "schema": {
          "type": "string",
          "examples": [
            "\"somalynk-json\";r=238;t=41"
          ]
        }
      },
      "RateLimitLimit": {
        "description": "Quota for the current window. The superseded draft-07 spelling, sent alongside RateLimit-Policy for older clients.",
        "schema": {
          "type": "integer",
          "examples": [
            240
          ]
        }
      },
      "RateLimitRemaining": {
        "description": "Requests left in the current window for this client.",
        "schema": {
          "type": "integer",
          "examples": [
            238
          ]
        }
      },
      "RateLimitReset": {
        "description": "Seconds until the current window resets and the quota is restored.",
        "schema": {
          "type": "integer",
          "examples": [
            41
          ]
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying. Sent only on 429.",
        "schema": {
          "type": "integer",
          "examples": [
            41
          ]
        }
      }
    },
    "parameters": {
      "page": {
        "name": "page",
        "in": "query",
        "description": "1-based page number.",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "perPage": {
        "name": "per_page",
        "in": "query",
        "description": "Records per page. Values outside 1..100 return HTTP 400 with code rest_invalid_param.",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 10
        }
      },
      "search": {
        "name": "search",
        "in": "query",
        "description": "Limit results to those matching a search term.",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "searchQuery": {
        "name": "search",
        "in": "query",
        "description": "The search term. Optional; omitting it returns unfiltered results.",
        "required": false,
        "schema": {
          "type": "string"
        },
        "example": "knee"
      },
      "slug": {
        "name": "slug",
        "in": "query",
        "description": "Limit results to one or more specific slugs.",
        "required": false,
        "schema": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "style": "form",
        "explode": true,
        "example": [
          "ulnar-nerve-slider"
        ]
      },
      "offset": {
        "name": "offset",
        "in": "query",
        "description": "Offset the result set by a specific number of records.",
        "required": false,
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "order": {
        "name": "order",
        "in": "query",
        "description": "Sort direction. Any other value returns HTTP 400.",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "asc",
            "desc"
          ],
          "default": "desc"
        }
      },
      "orderbyPost": {
        "name": "orderby",
        "in": "query",
        "description": "Sort key. Verified live: date and title.",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "date",
            "id",
            "include",
            "title",
            "slug",
            "modified",
            "relevance"
          ],
          "default": "date"
        }
      },
      "parent": {
        "name": "parent",
        "in": "query",
        "description": "Limit to pages whose parent matches one of these ids. Use 0 for top-level pages.",
        "required": false,
        "schema": {
          "type": "array",
          "items": {
            "type": "integer"
          }
        },
        "style": "form",
        "explode": true
      },
      "fields": {
        "name": "_fields",
        "in": "query",
        "description": "Comma-separated list of fields to return, which materially shrinks the response. Verified live: _fields=id,slug,link returns only those three keys.",
        "required": false,
        "schema": {
          "type": "string"
        },
        "example": "id,slug,link"
      },
      "searchType": {
        "name": "type",
        "in": "query",
        "description": "Object type to search.",
        "required": false,
        "schema": {
          "type": "string",
          "enum": [
            "post",
            "term",
            "post-format"
          ],
          "default": "post"
        }
      },
      "searchSubtype": {
        "name": "subtype",
        "in": "query",
        "description": "Narrow to a specific subtype, for example page or post.",
        "required": false,
        "schema": {
          "type": "string",
          "default": "any"
        },
        "example": "page"
      }
    },
    "responses": {
      "InvalidParam": {
        "description": "A query parameter failed validation. Body is the RestError envelope with code rest_invalid_param; data.params names each rejected parameter.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RestError"
            },
            "example": {
              "code": "rest_invalid_param",
              "message": "Invalid parameter(s): per_page",
              "data": {
                "status": 400,
                "params": {
                  "per_page": "per_page must be between 1 (inclusive) and 100 (inclusive)"
                }
              }
            }
          }
        },
        "headers": {
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimitState"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        }
      },
      "NotFound": {
        "description": "No route, or no object, matched the request. Body is the RestError envelope with code rest_no_route (unknown route, or a non-numeric id) or rest_post_invalid_id (well-formed id that does not exist or is not public).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RestError"
            },
            "examples": {
              "unknownRoute": {
                "summary": "Unknown route",
                "value": {
                  "code": "rest_no_route",
                  "message": "No route was found matching the URL and request method.",
                  "data": {
                    "status": 404
                  }
                }
              },
              "unknownPost": {
                "summary": "Unknown post id",
                "value": {
                  "code": "rest_post_invalid_id",
                  "message": "Invalid post ID.",
                  "data": {
                    "status": 404
                  }
                }
              }
            }
          }
        },
        "headers": {
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimitState"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        }
      },
      "ForbiddenContext": {
        "description": "The request asked for context=edit, which needs an authenticated editor. This API is anonymous, so it always fails. Body is the RestError envelope with code rest_forbidden_context. WordPress answers 401 here, not 403.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RestError"
            },
            "example": {
              "code": "rest_forbidden_context",
              "message": "Sorry, you are not allowed to edit posts in this post type.",
              "data": {
                "status": 401
              }
            }
          }
        },
        "headers": {
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimitState"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        }
      },
      "ServerError": {
        "description": "An unexpected server-side failure. Not produced in normal operation and not reproducible on demand, so treat it as a retryable transport-level fault. When WordPress can still serialise it the body is the RestError envelope with code internal_server_error; if PHP or the web server fails earlier than that the body may be HTML instead, so check the content type before parsing.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RestError"
            },
            "example": {
              "code": "internal_server_error",
              "message": "The site is experiencing technical difficulties.",
              "data": {
                "status": 500
              }
            }
          }
        },
        "headers": {
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimitState"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        }
      },
      "TooManyRequests": {
        "description": "The client exceeded the published rate limit for the JSON endpoints. Body is the RestError envelope with code rest_too_many_requests; data.retry_after and the Retry-After header carry the same number of seconds. The limit is per client address, per fixed window, and applies only to /wp-json and /api. HTML pages are not rate limited.",
        "headers": {
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimitState"
          },
          "RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RestError"
            },
            "example": {
              "code": "rest_too_many_requests",
              "message": "Too many requests. This client may make 240 requests per 60 seconds to the JSON endpoints. Retry after 41 seconds.",
              "data": {
                "status": 429,
                "retry_after": 41,
                "policy": {
                  "quota": 240,
                  "window": 60
                }
              },
              "links": {
                "documentation": "https://somalynk.com/developers/",
                "openapi": "https://somalynk.com/openapi.json"
              }
            }
          }
        }
      }
    },
    "schemas": {
      "RestError": {
        "type": "object",
        "title": "WordPress REST error envelope",
        "description": "Every error from this API is returned as JSON in this shape, with the HTTP status repeated in data.status. Verified live against somalynk.com for 400 (rest_invalid_param), 401 (rest_forbidden_context) and 404 (rest_no_route, rest_post_invalid_id). A 500 uses the same envelope with code internal_server_error when WordPress is still able to serialise it. A 429 uses the same envelope with code rest_too_many_requests. Branch on code, never on message.",
        "required": [
          "code",
          "message",
          "data"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Machine-readable error code.",
            "examples": [
              "rest_no_route",
              "rest_invalid_param",
              "rest_post_invalid_id",
              "rest_forbidden_context",
              "internal_server_error",
              "rest_too_many_requests"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable message. Wording may change; branch on code, not on message."
          },
          "data": {
            "type": "object",
            "required": [
              "status"
            ],
            "properties": {
              "status": {
                "type": "integer",
                "description": "The HTTP status code, repeated inside the body.",
                "examples": [
                  400,
                  401,
                  404,
                  429,
                  500
                ]
              },
              "params": {
                "type": "object",
                "description": "Present on rest_invalid_param: one entry per rejected parameter.",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "details": {
                "type": "object",
                "description": "Present on rest_invalid_param: per-parameter code and message.",
                "additionalProperties": true
              },
              "retry_after": {
                "type": "integer",
                "description": "Present on rest_too_many_requests: seconds until the quota window resets. Mirrors the Retry-After header."
              },
              "policy": {
                "type": "object",
                "description": "Present on rest_too_many_requests: the quota and window, in the same units as RateLimit-Policy.",
                "properties": {
                  "quota": {
                    "type": "integer"
                  },
                  "window": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "links": {
            "type": "object",
            "description": "Present on errors emitted by /api and on 429: absolute URLs to the documentation and the OpenAPI document. Never present on errors emitted by WordPress core.",
            "additionalProperties": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      },
      "RenderedText": {
        "type": "object",
        "description": "WordPress returns rendered fields as an object with a rendered key holding an HTML string.",
        "properties": {
          "rendered": {
            "type": "string",
            "description": "HTML."
          },
          "protected": {
            "type": "boolean"
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "description": "Site metadata plus registered namespaces and routes.",
        "properties": {
          "name": {
            "type": "string",
            "examples": [
              "Somalynk"
            ]
          },
          "description": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "home": {
            "type": "string",
            "format": "uri"
          },
          "gmt_offset": {
            "type": "string"
          },
          "timezone_string": {
            "type": "string"
          },
          "page_on_front": {
            "type": "integer"
          },
          "page_for_posts": {
            "type": "integer"
          },
          "show_on_front": {
            "type": "string"
          },
          "namespaces": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "routes": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Post": {
        "type": "object",
        "description": "A published blog post. Fields listed are those returned anonymously with context=view. Requesting context=edit returns HTTP 401 because this API is unauthenticated.",
        "properties": {
          "id": {
            "type": "integer",
            "examples": [
              993
            ]
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "description": "Site local time."
          },
          "date_gmt": {
            "type": "string",
            "format": "date-time"
          },
          "modified": {
            "type": "string",
            "format": "date-time"
          },
          "modified_gmt": {
            "type": "string",
            "format": "date-time"
          },
          "guid": {
            "$ref": "#/components/schemas/RenderedText"
          },
          "slug": {
            "type": "string",
            "examples": [
              "supine-lumbar-traction-clinic-adjunct-only"
            ]
          },
          "status": {
            "type": "string",
            "examples": [
              "publish"
            ]
          },
          "type": {
            "type": "string",
            "const": "post"
          },
          "link": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "$ref": "#/components/schemas/RenderedText"
          },
          "content": {
            "$ref": "#/components/schemas/RenderedText"
          },
          "excerpt": {
            "$ref": "#/components/schemas/RenderedText"
          },
          "author": {
            "type": "integer"
          },
          "featured_media": {
            "type": "integer"
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "_links": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Page": {
        "type": "object",
        "description": "A published page. Polylang translations appear as separate records with their own id, slug and link.",
        "properties": {
          "id": {
            "type": "integer",
            "examples": [
              1077
            ]
          },
          "date": {
            "type": "string",
            "format": "date-time"
          },
          "date_gmt": {
            "type": "string",
            "format": "date-time"
          },
          "modified": {
            "type": "string",
            "format": "date-time"
          },
          "modified_gmt": {
            "type": "string",
            "format": "date-time"
          },
          "guid": {
            "$ref": "#/components/schemas/RenderedText"
          },
          "slug": {
            "type": "string",
            "examples": [
              "tarifas"
            ]
          },
          "status": {
            "type": "string",
            "examples": [
              "publish"
            ]
          },
          "type": {
            "type": "string",
            "const": "page"
          },
          "link": {
            "type": "string",
            "format": "uri",
            "examples": [
              "https://somalynk.com/es/tarifas/"
            ]
          },
          "title": {
            "$ref": "#/components/schemas/RenderedText"
          },
          "content": {
            "$ref": "#/components/schemas/RenderedText"
          },
          "excerpt": {
            "$ref": "#/components/schemas/RenderedText"
          },
          "author": {
            "type": "integer"
          },
          "parent": {
            "type": "integer"
          },
          "menu_order": {
            "type": "integer"
          },
          "template": {
            "type": "string",
            "examples": [
              "somalynk-landing-v2"
            ]
          },
          "_links": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "SearchResult": {
        "type": "object",
        "description": "A compact search hit. Note that title is a plain string here, not a rendered object.",
        "properties": {
          "id": {
            "type": "integer",
            "examples": [
              957
            ]
          },
          "title": {
            "type": "string",
            "examples": [
              "Prone Knee Bend (Face-Down Control)"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "type": {
            "type": "string",
            "examples": [
              "post"
            ]
          },
          "subtype": {
            "type": "string",
            "examples": [
              "post",
              "page"
            ]
          },
          "_links": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "ApiDiscovery": {
        "type": "object",
        "title": "API discovery document",
        "description": "What GET /api returns: the names and locations of every machine-readable description of this site.",
        "required": [
          "name",
          "openapi",
          "api_base",
          "documentation"
        ],
        "properties": {
          "name": {
            "type": "string",
            "examples": [
              "Somalynk"
            ]
          },
          "description": {
            "type": "string"
          },
          "openapi": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL of this OpenAPI document."
          },
          "api_base": {
            "type": "string",
            "format": "uri",
            "description": "Base URL every path in this document is relative to."
          },
          "documentation": {
            "type": "string",
            "format": "uri",
            "description": "Human-readable developer page."
          },
          "llms_txt": {
            "type": "string",
            "format": "uri",
            "description": "Plain-text site index for agents."
          }
        }
      }
    }
  }
}
