API for property managers
Last updated November 2026
In short
- No code? Download the spreadsheet template, fill in one row per property, and upload it on your dashboard. You see what will change before anything is saved.
- Connecting a booking system or script? Make an API key on your dashboard and send up to 100 properties per request to the endpoints below.
- Give each property your own reference (
external_id, likeUNIT-12). Then sending the same data again updates the page instead of making a new one. - Part of Pro. You can have as many properties as your plan covers.
Properties and guest pages
Every property in Keegix has exactly one guest page (the link on your QR sign, keegix.com/p/…). The API gives you two views of the same thing:
- Properties are the settings: the name, your own reference, colours, the guest password, the sign style.
- Guest pages are what guests read: check-in, wifi, parking, house rules and the other sections.
You can create a property from either side. Everything follows the same rules as the editor: your plan's property count, guest passwords stored scrambled (never readable again), and when you change a section, its translation (if you have one) is removed so guests never see old instructions.
API keys
On your dashboard, under Manage many properties at once, type a name and tap Create key. The key starts with kgx_ and is shown only once: Keegix keeps a one-way scrambled copy, so nobody (including us) can read it back. If you lose it, revoke it and make another. You can have up to 10.
A key can read and change every one of your properties, so treat it like a password: keep it on your server, never in a web page or an app people download. Revoking a key stops it straight away.
Send the key with every request:
Authorization: Bearer kgx_your_key_here
(X-Api-Key: kgx_… works too.) All addresses start with https://keegix.com/api/. If your plan goes back to free, your keys pause and answer 402 until you're on Pro again.
How a row finds its property
Each item (or spreadsheet row) is matched against your properties only, in this order:
id: Keegix's id for the property (from a list call). No match is an error.slug: the end of the guest link,keegix.com/p/slug. No match is an error.external_id: your own reference, up to 100 characters. No match makes a new property with that reference.- None of these: a new property.
To give an existing property a reference, match it by id or slug and send external_id with it. Each reference can be used by one of your properties at a time. Send "external_id": null (JSON only) with an id to clear it.
Spreadsheet (CSV) upload
The easiest way: start from the template, save as CSV from Excel, Numbers or Google Sheets, and upload on your dashboard. Tap Check the file to see every change, then Save.
- First row: column names (any order; upper or lower case and spaces are fine, so
Wifi nameworks). - One row per property, up to 100 rows and 1 MB per file. Line breaks inside a cell are kept.
- An empty cell leaves that part as it is. It never wipes anything.
- Yes/no columns take
yes,no,true,false,1or0. - Commas or semicolons both work.
| Column | What it sets |
|---|---|
external_id, id, slug | Which property (see matching) |
name | Property name shown at the top of the guest page |
checkin, parking, rules, appliances, trash, local, emergency, checkout | The text of that section of the guest page (up to 6,000 characters each) |
wifi_name, wifi_password, wifi | Network name, password, and any wifi notes |
password / remove_password | Set a guest password (4–100 characters), or yes to remove it |
accent_color, bg_color | Colours, like #1f4d3a |
qr_style, qr_brand | Sign style (see styles) and yes/no for your brand colour on the sign |
helper_off | yes switches off the guest question helper on that page |
From a script, send the file itself:
# Check first (saves nothing)
curl -X POST "https://keegix.com/api/bulk-import?dry_run=1" \
-H "Authorization: Bearer $KEEGIX_KEY" \
-H "Content-Type: text/csv" \
--data-binary @properties.csv
# Then save
curl -X POST "https://keegix.com/api/bulk-import" \
-H "Authorization: Bearer $KEEGIX_KEY" \
-H "Content-Type: text/csv" \
--data-binary @properties.csv
A form upload with the file in a field called file works too. Each result has a row (the spreadsheet line, header = 1) and a label (the row's external_id or name).
Properties: list, create and update
List: GET /api/bulk-properties
Your properties, oldest first. Optional: ?page=1&per_page=200 (200 at most) and ?external_id=UNIT-12 to find one.
curl "https://keegix.com/api/bulk-properties?per_page=50" \
-H "Authorization: Bearer $KEEGIX_KEY"
{
"page": 1, "per_page": 50, "total": 2, "total_pages": 1, "plan_limit": 5,
"properties": [
{
"id": "k3j9x2m1q8w7e4r",
"external_id": "UNIT-12",
"name": "Harbour View Loft",
"slug": "h7kq2m9wxp",
"guest_url": "https://keegix.com/p/h7kq2m9wxp",
"status": "live",
"has_password": false,
"accent_color": "#1f4d3a",
"bg_color": "#f3f5f2",
"helper_off": false,
"qr_style": "tag-navy",
"qr_brand": false,
"created": "2026-11-04 15:02:11.120Z",
"updated": "2026-11-04 15:02:11.120Z"
}
]
}
status is live, paused (more properties than your plan covers: only the oldest ones stay live) or pending (made from a binder upload, waiting for the owner's first sign-in).
Create or update: POST /api/bulk-properties
Send up to 100 items. Only the fields you send change.
curl -X POST "https://keegix.com/api/bulk-properties" \
-H "Authorization: Bearer $KEEGIX_KEY" \
-H "Content-Type: application/json" \
-d '{
"properties": [
{ "external_id": "UNIT-12", "name": "Harbour View Loft", "qr_style": "tag-navy" },
{ "external_id": "UNIT-14", "name": "Garden Suite", "password": "garden2026" },
{ "slug": "h7kq2m9wxp", "external_id": "UNIT-9" }
]
}'
| Field | Type and rules |
|---|---|
id, slug, external_id | Text. Which property (see matching) |
name | Text, up to 100 characters |
accent_color, bg_color | Text like #1f4d3a |
password | Text, 4–100 characters. Stored scrambled; never returned |
remove_password | true removes the guest password |
helper_off | true or false: the guest question helper |
qr_style | tag-yellow, tag-navy, tag-dots, house, door, bubble, circle or square |
qr_brand | true or false: your accent colour on the sign |
Any other field is refused (so a typo doesn't go unnoticed). Photos and logos are added in the editor.
Guest pages: list, create and update
List: GET /api/bulk-pages
Same as the property list, with each page's content. per_page is 50 at most.
{
"page": 1, "per_page": 50, "total": 1, "total_pages": 1, "plan_limit": 5,
"pages": [
{
"id": "k3j9x2m1q8w7e4r", "external_id": "UNIT-12", "name": "Harbour View Loft",
"slug": "h7kq2m9wxp", "guest_url": "https://keegix.com/p/h7kq2m9wxp",
"status": "live", "has_password": false,
"content": { "checkin": "Check-in is from 4 pm…", "wifi": "", "wifi_name": "HarbourGuest", "wifi_password": "…", "parking": "…" },
"sections": [
{ "id": "a1b2c3d4e5f6", "type": "checkin", "title": "Check-in", "body": "Check-in is from 4 pm…", "enabled": true },
{ "id": "0f9e8d7c6b5a", "type": "wifi", "title": "Wifi", "body": "", "enabled": true, "wifi_name": "HarbourGuest", "wifi_password": "…" }
],
"updated": "2026-11-04 15:02:11.120Z"
}
]
}
Create or update: POST /api/bulk-pages
Match each page like a property (id, slug or external_id; name works here too), then send the content one of two ways:
content(easiest): an object with any ofcheckin,wifi,wifi_name,wifi_password,parking,rules,appliances,trash,local,emergency,checkout. Each fills the first section of that kind and leaves the others alone. If the page doesn't have that section, it's added. A section that gets text is switched on.sections(full control): the whole list of sections, in order, as the list call returns them. It replaces every section. Keep each section'sidso its photos stay with it; leaveidout for a new section.typeis one of the names above orcustom. Up to 40 sections.
curl -X POST "https://keegix.com/api/bulk-pages" \
-H "Authorization: Bearer $KEEGIX_KEY" \
-H "Content-Type: application/json" \
-d '{
"pages": [
{
"external_id": "UNIT-12",
"content": {
"checkin": "Check-in is from 4 pm. We text you the lockbox code on arrival day.",
"wifi_name": "HarbourGuest",
"wifi_password": "new-password-for-november"
}
}
]
}'
Sending "" for a content field empties it (an empty section isn't shown to guests).
What comes back
Create and update calls answer 200 with one result per item, in the order you sent them. One bad item never stops the others.
{
"dry_run": false,
"summary": { "created": 1, "updated": 1, "unchanged": 0, "failed": 1 },
"results": [
{ "index": 0, "ok": true, "action": "created", "changes": ["name", "qr_style", "external_id"], "property": { … } },
{ "index": 1, "ok": true, "action": "updated", "changes": ["password"], "property": { … } },
{ "index": 2, "ok": false, "error": "not_found", "message": "No property of yours has that slug." }
]
}
actioniscreated,updatedorunchanged(what you sent is already there, so nothing was written).changeslists the fields that changed.- Check first: add
?dry_run=1(or"dry_run": truein the body). Nothing is saved and actions readwould_create/would_update. - Results from
bulk-pagescarry apageinstead of aproperty.
Errors
Every error has a short error code and a plain-English message you can show to a person.
| Whole request | Meaning |
|---|---|
401 auth | No key, a mistyped key, or a revoked key |
402 pro_required | The account isn't on Pro (keys pause until it is) |
400 bad_request, bad_csv, unknown_column | The body or file couldn't be read; the message says what to fix |
413 too_many, too_big | Over 100 items or 1 MB: split it |
500 server | Our side. Send the same request again; anything already saved comes back unchanged |
| One item | Meaning |
|---|---|
not_found | No property of yours has that id or slug |
limit | Your plan's properties are all used. Add more from Manage billing |
duplicate | The same property appears twice in one request; only the first is used |
external_id_taken | Another of your properties already has that reference |
unknown_field | A field this endpoint doesn't take (often a typo) |
invalid, bad_colour, bad_password, bad_style | A value isn't allowed; the message says which and why |
Limits and good habits
- Up to 100 items per request, and up to 100 rows and 1 MB per CSV file.
- Send one request at a time and wait for the answer before the next. Larger lists: split into groups of 100.
- Always send an
external_idwhen you create a property. Then a request you send twice (for example after a dropped connection) updates instead of making a second page. - Leave door and lockbox codes out of the page if you can, and use a guest password if you must include them. Guest pages are reachable by anyone with the link.
- Checkout instructions should follow Airbnb's rules: no cleaning chores beyond the basics. See our Airbnb house rules guide.
Questions or something missing? Email contact@keegix.com.