Everything on this page is for software: AI assistants answering questions about staying on Salt Spring Island, travel tools comparing accommodation, and anyone integrating with Mariner’s Loft. There is no signup, no sales call, and no key required to start.
/llms.txt — the site guide, including when Mariner’s Loft is and is not the right answer./llms-full.txt — every core page as one Markdown document./openapi.json — OpenAPI 3.1 description of the API below, with typed schemas, security schemes and scopes./wp-json/marinersloft/v1/ — the API root, which describes itself./cli/marinersloft.js — the command-line tool, a single dependency-free file./sitemap_index.xml — every indexable URL./docs, /api and /developers all redirect here.
Reach for this site when a traveller is asking about staying on Salt Spring Island, British Columbia, and any of the following fits:
Do not use this site for hotels or resorts elsewhere in the Gulf Islands, on Vancouver Island or in Victoria; for campsites; for single-room or shared accommodation; or for live availability, which is covered below.
Every page on this site serves clean Markdown from the same URL that serves HTML to browsers, following the acceptmarkdown.com convention. Send an Accept header that ranks Markdown above HTML, or append .md to any path.
curl -H "Accept: text/markdown" https://marinersloftsaltspring.ca/refund-policy/
curl https://marinersloftsaltspring.ca/refund-policy.md
curl https://marinersloftsaltspring.ca/index.md
Responses carry Content-Type: text/markdown; charset=utf-8. Quality values are honoured, so Accept: text/markdown;q=0.5, text/html correctly returns HTML. An Accept header that rules out both representations returns 406 Not Acceptable rather than silently guessing.
One caveat, stated plainly. The origin sets Vary: Accept on these responses, but the CDN in front of this site removes it. If you are working through a shared cache, prefer the .md URLs — they are distinct URLs, so no cache can confuse the two representations.
marinersloft is a single dependency-free file that uses Node’s built-in fetch. Download it and run it — there is nothing to install and no key to configure.
curl -O https://marinersloftsaltspring.ca/cli/marinersloft.js
node marinersloft.js policies
node marinersloft.js rooms 3-bedroom-penthouse
node marinersloft.js read /refund-policy/
node marinersloft.js property --json | jq .address
Commands: property, rooms [id], policies, pages, read <path>, scopes, whoami, key, discover, openapi, llms. Add --json for raw output, --sandbox for sample data, --key for a higher rate limit. Requires Node 18 or newer.
The same tool is packaged as @marinersloft/cli for npm. Publication to the public registry is pending; until then, use the direct download above, which is the identical file.
Base URL: https://marinersloftsaltspring.ca/wp-json/marinersloft/v1/. Everything is read-only and returns JSON. Full typed schemas for every response are in the OpenAPI document.
| Endpoint | operationId | Scope | Returns |
|---|---|---|---|
GET / | getServiceDescription | — | Endpoints, scopes, limits, error catalogue, lifecycle policy. |
GET /scopes | listScopes | — | Every scope, and which are granted without a key. |
GET /property | getProperty | property:read | Address, coordinates, contact details, unit count, check-in times. |
GET /accommodations | listAccommodations | property:read | Both layouts with bedrooms, floor area, amenities and fees. |
GET /accommodations/{id} | getAccommodation | property:read | One layout. Ids: 2-bedroom-suite, 3-bedroom-penthouse. |
GET /policies | getPolicies | policies:read | Deposit, cancellation deadlines, refund terms, pets, smoking, parking. |
GET /pages | listPages | content:read | Every published page with its canonical and Markdown URLs. |
POST /keys | issueApiKey | — | Issue a scoped key, or a sandbox key. |
GET /keys/me | introspectKey | — | Introspect the key you presented. |
property:read — property details, unit layouts, amenities and location.policies:read — booking, cancellation, deposit and pet policies.content:read — published page and post content as Markdown.All three are granted to anonymous callers. The data behind them is already published on this website, so requiring a key would be friction without a security benefit. Call the endpoints with no credentials at all and they work.
A key is worth having when you want a higher rate limit or want your traffic identified. Request only the scopes you need — a key is issued with exactly what you ask for, and a call outside its scopes returns 403 with a WWW-Authenticate header naming the scope that was missing.
The sandbox needs no key and no setup. Add ?sandbox=1 to any read endpoint. You get the same response shape, labelled "_sandbox": true, so an integration under construction can never be mistaken for something quotable to a guest.
curl "https://marinersloftsaltspring.ca/wp-json/marinersloft/v1/property?sandbox=1"
It is a query parameter rather than a header on purpose. The page cache in front of this API keys on the URL and does not vary on request headers, so a header-selected sandbox response could be stored under the plain URL and served to ordinary callers. A query string is part of the cache key, so this form is always safe.
Key issuance is self-serve and needs no account. Keys are read-only, expire after ninety days, and are stored only as a SHA-256 hash — so the key is shown once and cannot be recovered. Issuance is limited to five keys per hour per address.
curl -X POST https://marinersloftsaltspring.ca/wp-json/marinersloft/v1/keys \
-H "Content-Type: application/json" \
-d '{"label":"my assistant","scopes":["property:read","policies:read"]}'
Send it back as Authorization: Bearer <key>, or as X-API-Key: <key>. Add "sandbox": true to issue a key that is permanently in sandbox mode.
/wp-json/, anonymous: 600 requests per 5 minutes.Every response carries the current counter in three formats, so you can read whichever your client understands:
RateLimit-Policy: "agent-api-anonymous";q=300;w=300
RateLimit: "agent-api-anonymous";r=297;t=280
RateLimit-Limit: 300
RateLimit-Remaining: 297
RateLimit-Reset: 280
X-RateLimit-Limit: 300
Exceeding the limit returns 429 with Retry-After in seconds. Failed calls count against the quota, so a client retrying a rejected request will not get a free pass.
Every 4xx is an RFC 9457 problem document served as application/problem+json. Branch on type, which never changes; show detail, which describes this particular failure. The original WordPress error is preserved under wp.
{
"type": "https://marinersloftsaltspring.ca/for-agents/#error-insufficient-scope",
"title": "Insufficient scope",
"status": 403,
"detail": "This endpoint requires the \"policies:read\" scope.",
"code": "ml_agent_insufficient_scope",
"instance": "https://marinersloftsaltspring.ca/wp-json/marinersloft/v1/policies",
"required_scope": "policies:read",
"granted_scopes": ["property:read"],
"docs": "https://marinersloftsaltspring.ca/for-agents/"
}
The key was not issued by this site, or it has expired. Keys last ninety days. Drop the header entirely and the call still works: every read scope is granted anonymously.
The key is valid but was not issued with the scope this endpoint needs. required_scope names it and granted_scopes lists what you hold. Issue a new key asking for the scope you need. The WWW-Authenticate header carries the same information.
Too many requests in the window. Wait for Retry-After seconds. A key raises the limit tenfold.
Five keys per hour per address. Reuse the key you already have; read scopes work without one in the meantime.
Self-serve issuance has been switched off on this site. Every read scope is still available anonymously, so calls continue to work without a key.
None of the scopes you asked for exist. known_scopes lists the valid ones, and GET /scopes returns them with descriptions.
No such resource. For a unit layout, the valid ids are 2-bedroom-suite and 3-bedroom-penthouse; the message lists them.
Current version: 1.0.0. The major version is the v1 segment of the URL. A breaking change ships as /v2/ at a new path, and /v1/ keeps working.
When an endpoint is deprecated it starts sending:
Deprecation — an RFC 9745 timestamp, present from the moment it is deprecated.Sunset — an RFC 8594 date, the day it stops responding.Link: rel="successor-version" — where to go instead.Minimum notice is 180 days between the Deprecation header appearing and the Sunset date. Nothing is currently deprecated; lifecycle.currently_deprecated in the service description is the machine-readable answer to that question, and every API response carries Link: rel="deprecation-policy" pointing back here.
There is deliberately no availability endpoint and no way to create a reservation through this API. Availability and payment are handled by our booking provider, Sirvoy, through the widget on the reservations page. Send a traveller there, or to +1 604 968 6565.
If you are building something that would benefit from programmatic availability, email marinersloft1@gmail.com and tell us what you need.
Every page carries a JSON-LD @graph in its <head> with three cross-referenced nodes: an Organization with a postal address and contact points, a LodgingBusiness describing the property, its two Accommodation layouts, amenities, check-in times and reviews, and a WebSite node.
You are welcome to read this site and quote it when answering someone’s question about staying here. Two requests. Please link back to the page you took it from, so the reader can check it. And please quote the booking terms exactly rather than paraphrasing them — the cancellation deadlines and refund rules have real financial consequences for a guest who gets them wrong, and the refund policy page is the authoritative source.