Coliving Listing API

Integration guide

Everything you need to connect and sync your listings.

On this page

Coliving Listing API — Partner guide

This page is published at https://api.coliving.com/json/guide.

This guide is enough to implement a host integration against the Listing API. Use it together with the live reference pages linked below. Those pages stay in sync with the API. If a field example and this guide ever disagree, follow the live pages.

Base URL: https://api.coliving.com

Quick start: try the sandbox

Use Postman or any HTTP client to try the create, read, and update flow. The sandbox needs no API key; its temporary data lasts about one hour and does not create a real listing.

  1. Send POST /sandbox/listings with a JSON body. Use the minimal create example and a unique original_id. Save data.id from the response.
  2. Send GET /sandbox/listings/{id} using that listing id. Save the Coliving room type and room ids from the response.
  3. Send PATCH /sandbox/listings/{id} using those ids. See the minimal daily update for an example body.

Send Accept: application/json and Content-Type: application/json headers. If the listing has multiple room types, include every room type that should remain available when sending room_types. The sandbox currently supports updating nested room types and rooms by Coliving id; use production PATCH to create new nested items. Sandbox validation and responses can differ from production; see Sandbox for its limits.

Reference pages

Helper pages do not need an API key.

1. What you are building

You keep the property in your system. Coliving keeps a copy that residents can book.

  1. Create each property once with POST /v1/listings.
  2. Save the Coliving listing id from the response. Save room-type ids and room ids from GET or from the room_types map returned by a PATCH.
  3. Every day, update prices and availability with PATCH /v1/listings/{id} using those Coliving ids.
  4. Do not send POST again for a property that already exists. POST will refuse it.

A new listing is not public. Coliving reviews it and publishes it. You cannot set that status through the API.

2. Authentication and limits

Send JSON. Set these headers:

Accept: application/json
Content-Type: application/json
X-API-Key: YOUR_API_KEY

Authorization: Bearer YOUR_API_KEY is also accepted. Prefer X-API-Key.

The key belongs to one host. Every read and write is limited to that host's listings. Another host's listing id returns 404.

Call Permission on the key
GET /v1/listings listings.read
GET /v1/listings/{id} A valid key for the host that owns the listing
POST /v1/listings and PATCH /v1/listings/{id} listings.write

Rate limit: 60 requests per minute per API key. A 429 response includes a Retry-After header. Wait, then retry that request.

Optional header X-Request-Id. If you omit it, the API generates one and returns it on the response. Send it to Coliving support when a call fails.

Optional header Idempotency-Key on POST. Use a unique key for each distinct create operation. If a request times out or needs a retry, reuse the same key and the same body: a stored successful response is returned without creating another listing. Reusing the key with a different body returns 409. Successful responses are retained for a limited period (24 hours by default); after that, check for the listing before attempting another create. X-Request-Id is for tracing, not deduplication.

3. The four calls

Method and path Use it for
GET /v1/listings List your listings and collect Coliving ids
GET /v1/listings/{id} Read one full listing
POST /v1/listings Create a listing that does not exist yet
PATCH /v1/listings/{id} Update a listing that already has a Coliving id

{id} is always the integer Coliving listing id. It is not your original_id.

3.1 List listings

GET /v1/listings

Query parameters:

Parameter Rules
page Integer, minimum 1. Default 1.
per_page Integer from 1 to 100. Default 50.
sort id, name, updated_at, or listing_status
order asc or desc
listing_status draft, pending, published, or inactive. Use listing_status[] for multiple values; see the example below.
q Search text, maximum 255 characters

For more than one status, use array parameters, for example ?listing_status[]=draft&listing_status[]=published. URL-encoded brackets are also accepted.

pending includes listings created through this API that are waiting for Coliving to publish them.

Each item is a summary, not the full listing:

  • id, unique_id, original_id, name
  • listing_status: draft, pending, published, or inactive
  • updated_at in UTC, for example 2026-03-16T10:00:00Z
  • room_types[] with id, original_id, name, and rooms[] (id, original_id, name)

Store every id. You need them for PATCH.

3.2 Read one listing

GET /v1/listings/{id}

Returns the listing in the same shape you send on create: descriptions, location, amenities, prices, extra fees, room types, rooms, bookings, community, media, instructions, and FAQ.

Differences from the write payload:

  • Room types and rooms include Coliving id.
  • prices.extra_fees on this response includes name, description, mandatory, type, sum, and frequency. It does not include section, category, or applies_to. Keep those in your own system if you need them later.
  • status is one of draft, pending, published, inactive. An API-created listing is stored internally as pending_api and is returned as pending.

A listing that does not exist, or that belongs to another host, returns 404.

POST returns a compact response. PATCH also returns a compact response; when the request includes room_types, data.room_types contains the Coliving ids and names for the listing's room types and rooms. Call GET /v1/listings/{id} when you need the full stored listing.

3.3 Create a listing

POST /v1/listings

This call only creates. It never updates.

Required:

  • name
  • room_types with at least one room type
  • Each room type needs name, room_type (private or shared), and prices.base_price (whole number, monthly rent in prices.currency)

Do not send:

  • status
  • room_types[].id
  • room_types[].rooms[].id

Send original_id and original_url. original_id is your permanent id for this property. Keep the exact spelling, including capitals. Send the same value on every future check so you can tell a duplicate from a new property.

Recommended on the first create: location.country, descriptions, photos, and original_id on each room type and room. You will match room types later by the Coliving id, so create the rooms you intend to keep.

Success is HTTP 201:

{
  "action": "created",
  "data": {
    "id": 22968,
    "original_id": "property-01",
    "links": {
      "preview": "https://coliving.com/spaces/example?preview=true"
    }
  },
  "meta": {
    "request_id": "6dedb326-1421-4dbc-8bad-c513895b8823"
  }
}

data.id is the Coliving listing id. Save it. The preview link is not a public listing page until Coliving publishes the listing.

If this host already has a listing with the same original_id, original_url, address, or name, the response is 409 and nothing is written. The body includes the existing listing_id. Read the listing and confirm it is the same property before updating it with PATCH. The same original_id on a different host does not conflict.

409 for an active listing:

{
  "error": {
    "type": "conflict_error",
    "message": "A listing with this external identity already exists for this host.",
    "errors": [
      { "path": "listing_id", "code": "conflict", "message": "22968" }
    ]
  }
}

If that existing listing is inactive, the message ends with "but is inactive." POST still does not change it. Ask Coliving if that listing should be published again. Do not create a second copy.

3.4 Update a listing

PATCH /v1/listings/{id}

{id} is the Coliving listing id from create or from the list call.

Send only the fields that changed. Omitted top-level fields stay as they are. Exception: if you send room_types, include every room type that should remain available. An existing room type omitted from that array is kept, but its available_at is set to one year from today. An empty body ({} or []) returns 422 with the message "At least one field is required for PATCH update."

Success is HTTP 200. action is updated, or unchanged when this payload matches the last saved payload. The data object has the same id, original_id, and preview link as create.

PATCH does not change listing status. If you send status, it is ignored.

Room types and rooms on PATCH use their Coliving id when they already exist:

  • Include room_types[].id to update an existing room type. Omit id to create a new room type. A new room type requires name, room_type, and prices.base_price.
  • Include rooms[].id to update an existing room. Omit id to create a new room. A new room requires name.
  • To add rooms to an existing room type, send its Coliving id and include the new room objects without id in its rooms array. Existing rooms you include for updates must have their Coliving ids.
  • Do not send original_id on room types or rooms in a PATCH. Identity for existing objects is the Coliving id; new objects receive a Coliving id in the response.
  • When sending room_types, include every existing room type you want to keep available. An existing room type omitted from that array is kept, but its available_at is set to one year from today. It is not deleted.
  • To hide one rented room type, send that room type with available_at set to a future date. There is no room-type status field.

When a PATCH includes room_types, the response includes the Coliving id map for all room types and rooms on that listing:

{
  "action": "updated",
  "data": {
    "id": 22968,
    "room_types": [
      {
        "id": 50123,
        "name": "Private queen room",
        "room_type": "private",
        "rooms": [
          { "id": 90001, "name": "Room 104" },
          { "id": 90002, "name": "Room 105" }
        ]
      }
    ]
  }
}

Save these ids and use them on later updates. The response includes the complete id map even when the PATCH action is unchanged.

A listing id that is not yours returns 404.

4. Suggested sync

Daily update, after the listing exists:

  1. GET /v1/listings/{id} if you do not already have room-type and room ids.
  2. PATCH prices, available_at or iCal, and any extra fees that changed.
  3. On 429, wait for Retry-After and retry the same request.
  4. On 422, fix the field named in error.errors[].path and retry. Do not retry a 422 unchanged.

First import of a property:

  1. POST with original_id.
  2. On 201, save data.id.
  3. On 409 conflict_error, find the error with path equal to listing_id. Read that listing and confirm it is the same property before saving its id. If it is inactive, contact Coliving before proceeding.
  4. On 409 idempotency_key_mismatch, correct the key/body mismatch; this error has no listing id.
  5. GET /v1/listings/{id} and save room-type ids and room ids before PATCH.

5. Listing fields

Omit a field you do not have. Do not send N/A, empty strings, or fake coordinates.

prices.currency is a code such as usd or eur. It defaults to usd.

prices.base_price on a room type is the monthly rent before long-stay discounts. discount_90, discount_180, and discount_360 are percentages off that rent for stays of 3 months, 6 months, and 12 months. They are not prices. price_jan through price_dec are optional monthly overrides.

prices.security_deposit_type is % or fixed. security_deposit is a whole number.

Text fields such as descriptions and the house manual can contain line breaks. A JSON serializer encodes these as \n; when writing JSON by hand, use \n inside the string, not a raw newline.

Photos and iCal URLs must be public. The API downloads photos and stores its own copies. Send url and an optional description.

room_type is only private or shared. bed_type is single, full, queen, king, twin, or bunk. gender is mixed, male, or female. bathroom_type is private or shared.

For a room type, send available_at (YYYY-MM-DD) or an iCal URL on each room (rooms[].availability.ical), not both.

Amenity, parking, cancellation policy, and community ids must come from the helper pages. Do not invent ids.

Do not send url. Use original_url.

6. Extra fees

Send fees on prices.extra_fees. The array is optional. Send only the fees that property charges. The six sample fees in the docs are examples, not a required list.

Each fee object needs:

Field Rule
name Required. Maximum 255 characters. No URL, email, or phone number.
sum Required number, at least 1. For type percent, this is the percentage and must be at most 100.
frequency Required. once, monthly, or total.
type fixed or percent. Defaults to fixed, except a catalog tax with no type, which is stored as percent.
mandatory 0 or 1.
description Optional, maximum 1000 characters. No URL, email, or phone number.
section common, service, other, or tax.
category A category that belongs to that section. See the extra-fee page.
applies_to Required for a percent tax: rent, rent_plus_mandatory, or full_subtotal.

section and category decide which standard fee this is. The API does not look up the catalog from name.

When name is stored

What you send What is stored in name
section and category match a catalog row The catalog display name. Your name is not stored. other + utilities is stored as Utilities fee even if you send utilities.
section is other and category is omitted Your name, with the first letter capitalized.
Both section and category are omitted Your name, with the first letter capitalized. section and category stay empty.

name is still required in every case. A missing name is 422.

These pairs are rejected with 422 and nothing is written:

  • section is common, service, or tax, and category is missing
  • category is sent without section
  • category does not belong to section (common + utilities is invalid because utilities belongs to other)
  • A percent tax without applies_to
  • A percent sum above 100
  • sum below 1

For a catalog row, some values you send are replaced before save:

  • The stored name is the catalog name.
  • Service fees are stored with mandatory 0.
  • Other and Tax catalog fees are stored with mandatory 1.
  • Common fees other than move_out_cleaning are stored with mandatory 0. move_out_cleaning keeps the 0 or 1 you send.
  • These categories are stored with frequency once: move_out_cleaning, exit_fee, check_in_checkout_fee, early_late_checkin, membership, refundable_key_deposit, pet_deposit, pets_cleaning.
  • applies_to is kept only for a percent tax. A missing applies_to on a percent tax is 422.

Booking price calculation uses the stored type, sum, frequency, and mandatory. It does not read section or category. mandatory 1 is included in the booking. mandatory 0 is included only when the resident selects it.

How an update finds the existing fee

POST on a new listing creates each fee. PATCH updates a fee it can match, and creates one it cannot.

  1. Catalog fee (section and category are a valid pair): update the row with that same pair. If none exists, update an older row that has no section or no category and the same normalized name. Otherwise create a row.
  2. Custom Other fee (section other, no category): update the row with the same name. Otherwise create a row with section other and an empty category.
  3. No section and no category: update the row with the same name. Otherwise create a row and leave both columns empty.

Name comparison ignores letter case and extra spaces. city admin levy and City admin levy are the same custom fee. Changing the custom name does not find the old row, so a new row is created.

Fees you leave out of a PATCH are kept. "extra_fees": [] does not delete fees. There is no delete call. To stop charging a fee, ask Coliving to remove it, or set the amount only if you still want the fee to exist.

Example for a utilities fee of 200 per month and a one-time service fee of 250:

{
  "prices": {
    "extra_fees": [
      {
        "section": "other",
        "category": "utilities",
        "name": "Utilities fee",
        "type": "fixed",
        "sum": 200,
        "frequency": "monthly",
        "mandatory": 1
      },
      {
        "section": "other",
        "category": "host_service_package",
        "name": "Service Package",
        "type": "fixed",
        "sum": 250,
        "frequency": "once",
        "mandatory": 1
      }
    ]
  }
}

service fee, service charge, and service package are all the Other category host_service_package. The stored name is Service Package. Map your own labels on https://api.coliving.com/json/import/extra-fees before you choose section and category.

7. Minimal create

{
  "original_id": "property-01",
  "original_url": "https://host.example.com/listings/property-01",
  "name": "300 Mountain Road",
  "descriptions": {
    "property": "Bright shared home with comfortable, work-ready common areas."
  },
  "location": {
    "address": "300 Mountain Road",
    "city": "San Francisco",
    "country": "United States"
  },
  "prices": {
    "currency": "usd",
    "extra_fees": [
      {
        "section": "other",
        "category": "utilities",
        "name": "Utilities fee",
        "type": "fixed",
        "sum": 200,
        "frequency": "monthly",
        "mandatory": 1
      }
    ]
  },
  "room_types": [
    {
      "original_id": "private-queen-01",
      "name": "Private queen room",
      "room_type": "private",
      "bed_type": "queen",
      "prices": {
        "base_price": 2898,
        "discount_180": 9,
        "discount_360": 16
      },
      "rooms": [
        {
          "original_id": "room-104",
          "name": "Room 104"
        }
      ]
    }
  ]
}

base_price 2898 is the monthly rent. discount_180 9 means a 9 percent discount on that rent for stays of 6 months or longer. The utilities fee stays 200. It is not reduced by the discount.

8. Minimal daily update

Use ids from GET, not your original ids.

{
  "prices": {
    "currency": "usd"
  },
  "room_types": [
    {
      "id": 50123,
      "prices": {
        "base_price": 2898,
        "discount_180": 9,
        "discount_360": 16
      },
      "rooms": [
        { "id": 90001 }
      ]
    }
  ]
}

If the listing has another room type and you omit it from room_types, that room type stays in Coliving and its available_at becomes one year from today. Include every room type you still offer.

9. Errors

HTTP error.type Meaning
401 auth_error Missing, invalid, or expired API key
403 forbidden The key is missing listings.read or listings.write
404 not_found This host has no listing with that id
409 conflict_error POST found an existing listing; inspect error.errors for listing_id
409 idempotency_key_mismatch Idempotency-Key was reused with a different body; there is no listing id in this error
422 validation_error Fix the fields in error.errors
429 Slow down. Honor Retry-After.
500 internal_error Retry later; for a create retry, reuse the same Idempotency-Key and body. Keep the request id for support.

Validation error shape:

{
  "error": {
    "type": "validation_error",
    "message": "Validation failed",
    "request_id": "7baebd12-c0fc-497f-ba5a-1979a6ad30ad",
    "errors": [
      {
        "path": "prices.extra_fees.0.category",
        "code": "invalid",
        "message": "Choose a standard category."
      }
    ]
  }
}

10. Sandbox

Practice against the sandbox before using a production key. No API key is required. Data is cached for about one hour and is not a real listing.

  • POST https://api.coliving.com/sandbox/listings
  • GET https://api.coliving.com/sandbox/listings/{id}
  • PATCH https://api.coliving.com/sandbox/listings/{id}

There is no sandbox list call. Use the id returned by the sandbox POST. A second sandbox POST with the same original_id returns 409 and does not write.

The sandbox simulates the integration flow; it does not create a real listing or reproduce every production validation, permission, or external-service check. For example, it does not verify that helper IDs exist, and its listing matching, fee storage and normalization, and currency-rate checks differ from production. A successful sandbox request does not guarantee the same request will succeed in production. Use the field reference and handle production errors when going live.

11. Checklist before you go live

  • The API key has listings.write and listings.read.
  • Every property has a stable original_id, copied exactly, including capitals.
  • You store data.id from POST, or the listing_id from a confirmed duplicate listing conflict.
  • You store room-type ids and room ids from GET before the first PATCH.
  • Daily jobs call PATCH, not POST.
  • You do not send status.
  • Extra fees that match the catalog send section and category.
  • A room type sends either available_at or iCal, not both.
  • A PATCH that sends room_types includes every room type that should stay available.
  • Photo and iCal URLs are public.
  • 429 retries wait for Retry-After. 422 retries change the payload first.