curl --fail-with-body -X PUT "https://maatstays.wtf/v1/listings/7c4b1e58-2a90-4f61-b3d2-5e8a1c0f7d24/pricing-mode" \
-H "Authorization: Bearer $MAAT_PARTNER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode":"partner","fallback_to_base_price":true}'const res = await fetch('https://maatstays.wtf/v1/listings/7c4b1e58-2a90-4f61-b3d2-5e8a1c0f7d24/pricing-mode', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.MAAT_PARTNER_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ mode: 'partner', fallback_to_base_price: true }),
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.code}: ${body.detail}`);
if (body.warnings.includes('no_rates_next_30_nights')) {
console.warn('this listing is not priced for the coming month');
}
import os
import requests
res = requests.put(
"https://maatstays.wtf/v1/listings/7c4b1e58-2a90-4f61-b3d2-5e8a1c0f7d24/pricing-mode",
headers={"Authorization": f"Bearer {os.environ['MAAT_PARTNER_TOKEN']}"},
json={"mode": "partner", "fallback_to_base_price": True},
timeout=30,
)
body = res.json()
if res.status_code >= 400:
raise RuntimeError(f"{body['code']}: {body.get('detail')}")
if "no_rates_next_30_nights" in body["warnings"]:
print("this listing is not priced for the coming month")
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
)
func main() {
req, _ := http.NewRequest(http.MethodPut, "https://maatstays.wtf/v1/listings/7c4b1e58-2a90-4f61-b3d2-5e8a1c0f7d24/pricing-mode", strings.NewReader(`{"mode":"partner","fallback_to_base_price":true}`))
req.Header.Set("Authorization", "Bearer "+os.Getenv("MAAT_PARTNER_TOKEN"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
var problem struct {
Code string `json:"code"`
Detail string `json:"detail"`
}
_ = json.NewDecoder(res.Body).Decode(&problem)
log.Fatalf("%d %s: %s", res.StatusCode, problem.Code, problem.Detail)
}
var body struct {
Mode string `json:"mode"`
Warnings []string `json:"warnings"`
}
if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
log.Fatal(err)
}
fmt.Println(body.Mode, body.Warnings)
}
{
"listing_id": "7c4b1e58-2a90-4f61-b3d2-5e8a1c0f7d24",
"mode": "partner",
"fallback_to_base_price": true,
"rules_deactivated": {
"pricing": 2
},
"coverage_until": null,
"warnings": []
}Become the listing's pricing source
Make your rates the listing’s price.
From this point the nightly rate a guest is quoted comes from the calendar you
pushed, and the min_stay you set on a night binds any stay that includes it.
Taking the mode switches the listing’s own pricing rules off — Maat’s rules and your rates would otherwise be two answers to one question. Releasing does not switch them back on; the host re-enables the ones they still want.
What happens to a night you have not priced is yours to choose, with
fallback_to_base_price.
Left false, a night with no rate cannot be quoted. It is not sold at the
host’s base price and it is not sold cheaply — it is refused, because a gap
means the feed is broken and guessing a price for it would under-charge a peak
night silently. Fill the horizon before you take the mode.
Set true, an unpriced night falls back to the host’s own base price instead
of refusing. Choose it when your calendar covers a rolling horizon rather than
every night you are contracted for: the listing stays bookable past the end of
your feed instead of falling off sale. Your rates still win on every night you
have sent one for. A listing whose host has no usable base price is refused
either way — a fallback that could reach zero would put a stay on sale free.
warnings carries no_rates_next_30_nights when the coming month is not
fully priced, under either setting, and taking the mode anyway is allowed:
building an integration incrementally is a legitimate thing to do.
Idempotent for you. A 409 pricing_mode_conflict means another source prices
this listing — a pricing tool connected in the Maat app. This API does not name
it; release it from the Maat app first.
Only nightly-stay listings have a rate calendar; an experience or a service is
409 inventory_not_supported.
Live in a sandbox workspace. Returns 501 against a production one, where no
mode is taken and no rule is switched off.
curl --fail-with-body -X PUT "https://maatstays.wtf/v1/listings/7c4b1e58-2a90-4f61-b3d2-5e8a1c0f7d24/pricing-mode" \
-H "Authorization: Bearer $MAAT_PARTNER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"mode":"partner","fallback_to_base_price":true}'const res = await fetch('https://maatstays.wtf/v1/listings/7c4b1e58-2a90-4f61-b3d2-5e8a1c0f7d24/pricing-mode', {
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.MAAT_PARTNER_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ mode: 'partner', fallback_to_base_price: true }),
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.code}: ${body.detail}`);
if (body.warnings.includes('no_rates_next_30_nights')) {
console.warn('this listing is not priced for the coming month');
}
import os
import requests
res = requests.put(
"https://maatstays.wtf/v1/listings/7c4b1e58-2a90-4f61-b3d2-5e8a1c0f7d24/pricing-mode",
headers={"Authorization": f"Bearer {os.environ['MAAT_PARTNER_TOKEN']}"},
json={"mode": "partner", "fallback_to_base_price": True},
timeout=30,
)
body = res.json()
if res.status_code >= 400:
raise RuntimeError(f"{body['code']}: {body.get('detail')}")
if "no_rates_next_30_nights" in body["warnings"]:
print("this listing is not priced for the coming month")
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
)
func main() {
req, _ := http.NewRequest(http.MethodPut, "https://maatstays.wtf/v1/listings/7c4b1e58-2a90-4f61-b3d2-5e8a1c0f7d24/pricing-mode", strings.NewReader(`{"mode":"partner","fallback_to_base_price":true}`))
req.Header.Set("Authorization", "Bearer "+os.Getenv("MAAT_PARTNER_TOKEN"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
var problem struct {
Code string `json:"code"`
Detail string `json:"detail"`
}
_ = json.NewDecoder(res.Body).Decode(&problem)
log.Fatalf("%d %s: %s", res.StatusCode, problem.Code, problem.Detail)
}
var body struct {
Mode string `json:"mode"`
Warnings []string `json:"warnings"`
}
if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
log.Fatal(err)
}
fmt.Println(body.Mode, body.Warnings)
}
{
"listing_id": "7c4b1e58-2a90-4f61-b3d2-5e8a1c0f7d24",
"mode": "partner",
"fallback_to_base_price": true,
"rules_deactivated": {
"pricing": 2
},
"coverage_until": null,
"warnings": []
}Authorizations
A short-lived partner token, obtained by exchanging your API key at
POST /token. Send it as Authorization: Bearer <access_token>.
Tokens live for fifteen minutes by default and never for more than an hour. Mint one per batch of work, not one per request.
The token carries the workspace and the environment its key was issued for. You cannot change either by asking: the exchange ignores everything in the request body for exactly that reason.
It also carries the key's scopes (scope, a list). Every operation names a
scope as x-maat-scope, and a token whose key lacks it is refused with
403 insufficient_scope, naming the scope. Scopes are fixed when a key is
issued and are re-read from the key on every request, so a key narrowed today is
narrowed on its next call — the token's scope is what the key held at exchange
time, for your own reading, never what authorizes a request.
x-maat-scope is the floor, and on the quick-action operations it is not the
whole requirement. PUT and DELETE /listings/{listing_id}/quick-actions/{template_key}
carry rules:write, and additionally require pricing:write when
template_key begins pricing. — the capability depends on the template in
the path, because a cleaning fee changes what a guest pays and a minimum stay
does not. The refusal is the same 403 insufficient_scope, naming
pricing:write. The two READS apply the same requirement without erroring:
GET /listings/{listing_id}/quick-actions still lists the pricing.* rows but
withholds their applied state, and GET /listings/{listing_id}/rules omits the
pricing rules entirely and reports pricing_withheld in warnings. These four
are the only operations in this document whose requirement is not exactly their
x-maat-scope, and each of them says so machine-readably:
x-maat-scope-additional lists the second scope it can require and
x-maat-scope-additional-when says what happens without it.
Never send it as a cookie or in a query string, and never store it — store the API key, mint tokens from it.
Path Parameters
Maat's identifier for the listing.
Body
partner is the only value. Releasing is DELETE on the same path, not
{"mode":"rules"} — one verb that both takes and releases is a verb a retry can
get backwards.
Always partner. There is one mode to take; releasing is a DELETE, not another mode.
"partner""partner"
What a night you have sent no rate for is worth.
false (the default) refuses to quote it — a gap means your feed is
broken, and inventing a price for a peak night would under-charge it
silently. true prices it at the host's own base price, so a listing
stays bookable past the end of your horizon. Your rates always win on
the nights you have sent.
A PUT states the whole mode, so omitting this field sets it to
false — send it on every take, not only the one that turns it on.
Changing it on a listing you already price is allowed and takes effect
on the next quote.
true
Response
You now price this listing.
The listing whose pricing mode you now hold.
Always partner — the mode you now hold.
"partner"What is in force for this listing now — true when an unpriced night falls back to the host's base price, false when it is refused.
true
The listing's own pricing rules this switched off. Booking rules are switched off too, a moment later, and are deliberately not counted here rather than reported as a number that could be stale by the time you read it.
Show child attributes
Show child attributes
The last night you have sent a rate for, or null when you have sent none yet.
"2027-01-31"
no_rates_next_30_nights when the coming month is not fully priced. A
warning, never a refusal. What those nights are worth depends on
fallback_to_base_price: unquotable until you send a rate when it is
false, the host's base price when it is true.
Something worth knowing about the mode you now hold. A warning, never a refusal.
no_rates_next_30_nights []