{"openapi":"3.1.0","info":{"title":"HorseHQ API","version":"1.0.0","summary":"Public HTTP surface of HorseHQ, barn management software for equestrian businesses.","description":"HorseHQ is barn management software for equestrian businesses. It combines scheduling, horse care and health records, staff coordination, recurring billing, invoicing and online payments in one platform, with an AI assistant (Ask HQ) that answers questions about a barn in English or Spanish.\n\nMost of HorseHQ's data is reached through one authenticated GraphQL endpoint rather than a REST resource per entity. Every record belongs to exactly one organization (a barn), and a request can only ever read or write the records of the organization its session is active in — there is no anonymous or cross-organization read.\n\nRate limits: the unauthenticated endpoints below are throttled per client IP — 10 requests a minute on each lead-capture form, 20 a minute on the health probe. Over the limit answers HTTP 429 with `Retry-After: 60`; honour it, and back off exponentially if you keep being refused. The authenticated GraphQL endpoint has no published per-operation quota today, but may still be throttled — treat a 429 there the same way. These are the enforced numbers, not guidance.","contact":{"name":"HorseHQ support","email":"help@horsehq.com","url":"https://horsehq.com/contact"},"termsOfService":"https://horsehq.com/terms-of-service","license":{"name":"Proprietary — © Horse HQ, Inc.","identifier":"LicenseRef-Proprietary"}},"servers":[{"url":"https://api.horsehq.com","description":"Production"}],"tags":[{"name":"Discovery","description":"Machine-readable descriptions of this API."},{"name":"Operations","description":"Service status, for monitors and for triage."},{"name":"Contact","description":"Public lead-capture forms on the marketing site. No authentication."},{"name":"GraphQL","description":"The authenticated data endpoint every application feature is built on."}],"paths":{"/openapi.json":{"get":{"operationId":"getOpenApiDocument","summary":"Fetch this OpenAPI document","description":"Returns the OpenAPI 3.1 description of this API. Unauthenticated, unchanging between deploys except when the surface changes, and safe to cache. The same document is published on the marketing origin at /openapi.json.","tags":["Discovery"],"security":[],"responses":{"200":{"description":"The OpenAPI document.","content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}}}}},"/api/health":{"get":{"operationId":"getServiceHealth","summary":"Check service health","description":"Reports whether the database, both authentication instances and the GraphQL server are reachable from the API process. Unauthenticated. Answers 200 when every check passes and 503 when any of them fails; the body has the same shape either way, so read `checks` to see which one is down.","tags":["Operations"],"security":[],"responses":{"200":{"description":"Every check passed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthReport"}}}},"429":{"description":"Too many requests from this client. Wait 60 seconds, or until `Retry-After` says, and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ThrottleRejected"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","example":60}}}},"503":{"description":"At least one check failed. Read `checks` for which.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthReport"}}}}}}},"/api/waitlist":{"post":{"operationId":"joinWaitlist","summary":"Join the product waitlist","description":"Records an email address as a marketing lead. Unauthenticated, and intended for the waitlist form on the marketing site. Sends no email. In production a reCAPTCHA v3 token obtained in the browser is required, so this operation is not usable server-to-server.","tags":["Contact"],"security":[],"requestBody":{"required":true,"description":"The address to record.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitlistRequest"}}}},"responses":{"200":{"description":"The address was recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormAccepted"}}}},"400":{"description":"The address was missing, malformed or over `maxLength`, a field this form does not accept was sent, or reCAPTCHA verification failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormRejected"}}}},"413":{"description":"The request body was larger than this form accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormRejected"}}}},"429":{"description":"Too many requests from this client. Wait 60 seconds, or until `Retry-After` says, and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormRejected"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","example":60}}}},"500":{"description":"The address could not be recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormRejected"}}}}}}},"/api/book-demo":{"post":{"operationId":"requestProductDemo","summary":"Request a product demo","description":"Submits a demo request to the sales team and records the sender as a marketing lead. Unauthenticated, and intended for the demo form on the marketing site. In production a reCAPTCHA v3 token obtained in the browser is required, so this operation is not usable server-to-server.","tags":["Contact"],"security":[],"requestBody":{"required":true,"description":"Who is asking, and how to reach them.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookDemoRequest"}}}},"responses":{"200":{"description":"The request reached the sales team.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormAccepted"}}}},"400":{"description":"A required field was missing, a field was over `maxLength`, a field this form does not accept was sent, or reCAPTCHA verification failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormRejected"}}}},"413":{"description":"The request body was larger than this form accepts.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormRejected"}}}},"429":{"description":"Too many requests from this client. Wait 60 seconds, or until `Retry-After` says, and retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormRejected"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","example":60}}}},"500":{"description":"The request could not be delivered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FormRejected"}}}}}}},"/api/graphql":{"post":{"operationId":"executeGraphqlOperation","summary":"Execute a GraphQL query or mutation","description":"The single authenticated data endpoint: horses, events and the calendar, people and memberships, invoices, payments, notes and tasks are all read and written here.\n\nAuthentication is the session cookie set by the sign-in flow on the web apps; there is no public API key programme yet, so a caller that cannot present a browser session cannot use this endpoint. Send the `x-horsehq-app` header so the request resolves against the right application. Schema introspection is disabled in production; write to help@horsehq.com for the schema.\n\nAn unauthenticated request receives 200 with a GraphQL error carrying `extensions.code = \"UNAUTHENTICATED\"`, per the GraphQL over HTTP convention, rather than a 401.","tags":["GraphQL"],"security":[{"sessionCookie":[]}],"parameters":[{"name":"x-horsehq-app","in":"header","required":false,"description":"Which application surface the session belongs to. Defaults to the operator app.","schema":{"type":"string","enum":["pro","rider"]}}],"requestBody":{"required":true,"description":"A GraphQL document, its variables and its operation name.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphqlRequest"}}}},"responses":{"200":{"description":"A GraphQL response. `errors` may be present with or without `data`; check it before reading `data`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphqlResponse"}}}},"400":{"description":"The request body was not a valid GraphQL request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphqlResponse"}}}},"429":{"description":"The caller is being throttled. Back off and retry, honouring `Retry-After` when present.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GraphqlResponse"}}}}}}}},"components":{"securitySchemes":{"sessionCookie":{"type":"apiKey","in":"cookie","name":"horsehq.session_token","description":"The session cookie issued by the sign-in flow. Scoped to `.horsehq.com`, so it travels to the API origin on its own. Obtained by a person signing in — there is no machine credential flow today."}},"schemas":{"HealthReport":{"type":"object","required":["status","service","timestamp","checks"],"properties":{"status":{"type":"string","enum":["ok","degraded"],"description":"Rolls up every entry of `checks`."},"service":{"type":"string","description":"Which service answered."},"timestamp":{"type":"string","format":"date-time","description":"When the checks ran."},"checks":{"type":"object","required":["db","authPro","authRider","graphqlReady"],"properties":{"db":{"type":"string","enum":["ok","fail"],"description":"The database answered a trivial query."},"authPro":{"type":"string","enum":["ok","fail"],"description":"The operator app auth instance booted."},"authRider":{"type":"string","enum":["ok","fail"],"description":"The rider app auth instance booted."},"graphqlReady":{"type":"string","enum":["ok","fail"],"description":"The GraphQL server finished starting."}}}}},"WaitlistRequest":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","minLength":1,"maxLength":254,"description":"The address to add to the waitlist. Rejected if it is not shaped like an address."},"recaptchaToken":{"type":"string","description":"A reCAPTCHA v3 token for the `waitlist` action. Required in production."}},"additionalProperties":false},"BookDemoRequest":{"type":"object","required":["name","email","phone"],"properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"The requester's full name."},"email":{"type":"string","format":"email","minLength":1,"maxLength":254,"description":"Where to reply. Rejected if it is not shaped like an address."},"phone":{"type":"string","minLength":1,"maxLength":32,"description":"A phone number to reach the requester on."},"barnName":{"type":"string","maxLength":120,"description":"The name of their barn or business. Optional."},"comments":{"type":"string","maxLength":2000,"description":"Anything they want the sales team to know first. Optional."},"recaptchaToken":{"type":"string","description":"A reCAPTCHA v3 token for the `book_demo` action. Required in production."}},"additionalProperties":false},"ThrottleRejected":{"type":"object","required":["error","message"],"description":"The body a throttled endpoint answers with, outside the two forms (which use FormRejected).","properties":{"error":{"type":"string","description":"Machine-readable reason. `too_many_requests` on this response."},"message":{"type":"string","description":"The same reason, in a sentence fit to show a person."}}},"FormAccepted":{"type":"object","required":["ok"],"properties":{"ok":{"type":"boolean","const":true,"description":"Always true on this response."}}},"FormRejected":{"type":"object","required":["ok","error"],"properties":{"ok":{"type":"boolean","const":false,"description":"Always false on this response."},"error":{"type":"string","description":"What was wrong, in a sentence fit to show a person."}}},"GraphqlRequest":{"type":"object","required":["query"],"properties":{"query":{"type":"string","description":"The GraphQL document to execute."},"operationName":{"type":"string","description":"Which operation in the document to run."},"variables":{"type":"object","additionalProperties":true,"description":"The operation's variables."}}},"GraphqlResponse":{"type":"object","properties":{"data":{"type":["object","null"],"additionalProperties":true,"description":"The result, shaped by the operation. Null when execution failed outright."},"errors":{"type":"array","description":"Present when anything went wrong. `extensions.code` carries the machine-readable reason.","items":{"type":"object","required":["message"],"properties":{"message":{"type":"string"},"path":{"type":"array","items":{"type":["string","integer"]}},"extensions":{"type":"object","additionalProperties":true}}}}}}}}}