Skip to content

Channel managers and OTAs

A channel manager, online travel agency (OTA) or booking portal does three things with Grondia. It reads what a campground has free and at what rate. It pushes the reservations it sells. And it keeps both sides in step when something changes, whether the change came from your channel, from the campground’s own booking page or from its reception desk.

This page walks through each step with the calls, the refusals to expect and a checklist for going live. It assumes you have read Core concepts and made the first call in the Quickstart. Examples use the made-up Camping Beispielsee, where Anna Müller books pitch A12.

  • Its pitches on your channel, with live numbers. The counts in the feed come from the same availability check the campground’s own booking page uses, so the feed does not show a pitch as free that a booking would then find taken. (The feed can be a few minutes old, see below.)
  • No double bookings. Your booking goes through the same overlap protection as a booking typed in at reception. If the pitch is gone, the call answers 409 and nothing is created.
  • Your bookings in its normal booking list. A booking you create carries your channel label, which the campground sees in its bookings list and in the bookings CSV export.
  • No per-campground set-up for the feed beyond an API key.
  1. The arrangement for payment and confirmation. See the note above. If your channel collects the guest’s money, tell the campground: there is no call to record that payment in Grondia, so the campground has to know.
  2. A key with the right scopes. A campground’s administrator creates keys in the Grondia admin, but the admin cannot create the scopes a channel manager needs: ari:read, ari:write and webhooks:write. Write to Felix, felix@grondia.com, or to Siro, siro@grondia.com, say which campground and which scopes, and we set the key up. The campground needs the API module as well (Credentials).
  1. Every few minutes

    1. Your channel manager to Grondia API: GET ari/. Availability, rates, restrictions

    2. Grondia API to Your channel manager: Categories with one row per night

  2. Your channel manager: A guest books on your channel

  3. Your channel manager to Grondia API: POST bookings/. With external_reference

  4. Grondia API to Your channel manager: 201 pending booking, or 409 or 422

  5. Grondia API to Your webhook endpoint: booking.created

  6. Later the booking is paid or confirmed

  7. Grondia API to Your webhook endpoint: booking.confirmed

  8. Your channel manager: The guest cancels on your channel

  9. Your channel manager to Grondia API: POST bookings/{id}/cancel/

  10. Grondia API to Your channel manager: 200 cancelled, with the refund

  11. Grondia API to Your webhook endpoint: booking.cancelled

  12. Every few minutes

    1. Your channel manager to Grondia API: GET bookings/. With updated_since

    2. Grondia API to Your channel manager: Every booking changed since the last sweep

Webhooks tell you quickly that something changed. The sweep at the bottom catches a webhook that never arrived, a change made at reception and the campground’s own bookings. Neither of them sees a booking the campground deletes (Syncing changes).

Paths are relative to https://api.campone.ch/api/v1/public/. The full table is in Credentials.

Scope What a channel manager uses it for
ari:read GET ari/ and GET ari/restrictions/. A key with sites:read is accepted for both reads too, so the sandbox key can already call them.
ari:write PUT ari/restrictions/, only if you set stop-sell or minimum stays yourself.
bookings:write POST bookings/ and POST bookings/{id}/cancel/.
bookings:read GET bookings/ for the sweep, and GET bookings/{id}/.
webhooks:write Register a webhook and read its delivery log.

Ask for what you use and no more: the operator sees every scope on the key. Guest names and email come back redacted on every read, because bookings:pii cannot be granted yet (see Personal data). Keep your own copy of what the guest gave you.

  • Every stay date is a calendar date, YYYY-MM-DD, in the campground’s own calendar, not UTC. The feed’s default start is today in the campground’s time zone. For a Swiss campground that is Europe/Zurich.
  • A feed row is a night. The row dated 2027-07-14 prices the night that starts on that day. A stay from 2027-07-14 to 2027-07-17 uses the rows of the 14th, 15th and 16th.
  • Timestamps in answers are ISO 8601 in UTC, such as updated_at. updated_since accepts an ISO date or datetime. A value without an offset is read in the campground’s time zone, so send a Z or an offset to say what you mean. Dates, times and money has the rest.

The ARI feed (availability, rates and inventory) returns every category the campground sells, with a row per night, in one call. A category is a site type, such as “Standard pitch” or “Cabin”, and code is its stable key.

Terminal window
curl "https://api.campone.ch/api/v1/public/ari/?start=2027-07-14&end=2027-07-18" \
-H "Authorization: Bearer $GRONDIA_API_KEY"

The answer, shortened to one category:

{
"tenant": "beispielsee",
"currency": "CHF",
"start": "2027-07-14",
"end": "2027-07-18",
"categories": [
{
"code": "standard",
"name": "Standard pitch",
"online_bookable": true,
"max_occupancy": 6,
"capacity_total": 18,
"restrictions": {
"min_nights": 2,
"max_nights": 0,
"booking_cutoff_hour": 18,
"max_booking_date": null
},
"days": [
{
"date": "2027-07-14",
"available": 4,
"price": "42.00",
"closed": false,
"min_stay": 2,
"closed_to_arrival": false,
"closed_to_departure": false
},
{
"date": "2027-07-15",
"available": 1,
"price": "42.00",
"closed": false,
"min_stay": 2,
"closed_to_arrival": false,
"closed_to_departure": false
},
{
"date": "2027-07-16",
"available": 2,
"price": "42.00",
"closed": false,
"min_stay": 2,
"closed_to_arrival": false,
"closed_to_departure": false
},
{
"date": "2027-07-17",
"available": 0,
"price": "42.00",
"closed": true,
"min_stay": 2,
"closed_to_arrival": true,
"closed_to_departure": false
}
]
}
]
}

The stay used in the rest of this page, 14 to 17 July, takes the nights of the 14th, 15th and 16th, and all three are open. The 17th is sold out and closed to arrival, which does not touch a guest who leaves that morning: for a departure you read closed_to_departure on that row, and it is false.

The parameters, all optional:

Parameter Meaning
start First night, inclusive. Default: today. A date in the past is moved to today.
end Last night exclusive (on the restrictions call it is inclusive, see Date windows in the ARI feed). Default: start plus 90 days. A window longer than 370 days is cut to 370 days, not refused. An end that is not after start answers 400.
site_type_code Only this category.

The server keeps each answer for five minutes per campground, window and category, so asking more often returns the same numbers. A pull every five minutes is enough: at most 12 calls an hour for one window, against a default of 1000 requests an hour for each key (Credentials).

Field What it means
available Free pitches in this category for this night, by the same check that decides whether a booking is accepted. Pitches held back for a guest the campground still has to place after an overbooking are already subtracted.
price The nightly pitch rate for this category and night. A seasonal price that covers the night wins over the category’s base price. When pitches in one category have different base prices, the feed shows the lowest. One rate per category: there are no rate plans.
closed true when you cannot book this night: nothing is free, the night is past the campground’s last bookable date (restrictions.max_booking_date), or the campground has set a stop-sell.
min_stay The minimum number of nights the campground has set for a stay that arrives on this date in this category: the category’s own seasonal minimum if it has one, otherwise the flat campground minimum, raised by any per-date minimum. It does not include a campground-wide seasonal or public-holiday minimum, which can be stricter. The create call applies seasonal minimums only for a campground that has switched them on (or that uses Grondia’s newer pricing), and then refuses a shorter stay with 400. The code is season_min_nights_violation when the seasonal minimum is the rule that refuses and min_nights_violation when the flat minimum is.
closed_to_arrival true when no stay may arrive on this date.
closed_to_departure true when no stay may leave on this date. Read it on the check-out date, so ask for a window that reaches one day past the last departure you care about.
restrictions Rules for the whole category. min_nights is the campground-wide minimum, the same in every category. max_nights is the strictest maximum for the category, 0 for none. booking_cutoff_hour is the local hour after which a stay arriving tomorrow can no longer be booked online, 0 for none. max_booking_date is the last arrival date sold online, null for no limit.
online_bookable Whether the campground sells this category online. Do not offer a category where it is false.

The feed counts only pitches that are active and open to the public. Annual pitches (Jahresplätze, rented for a whole season, see Dauergast) and pitches the campground keeps for reception are not in the numbers. currency is the campground’s, and so is the currency of every amount in a booking (Which currency).

A campground can set a minimum stay per category and season, and a category’s minimum may be lower than the campground-wide one. A cabin that rents from two nights beside pitches that need five is a real case. Read min_stay on the row of the arrival date for the category you are selling, never restrictions.min_nights, which is only the campground-wide floor:

{
"categories": [
{
"code": "standard",
"restrictions": { "min_nights": 5 },
"days": [{ "date": "2027-10-01", "min_stay": 5 }]
},
{
"code": "cabin",
"restrictions": { "min_nights": 5 },
"days": [{ "date": "2027-10-01", "min_stay": 2 }]
}
]
}

(This excerpt keeps only the fields that matter; a real row has all seven.) Resolve the minimum per category and per arrival date, and do not store one number per campground. Treat min_stay as the lowest number of nights that can work: the create call has the final word (next section).

The feed can be up to five minutes old, and it does not carry every rule the booking call applies. A seasonal or public-holiday minimum stay that the campground has set for the whole campground (not for one category), weekly letting for rental units (arrivals on one fixed weekday), a campground that is closed outside its season, a minimum notice per category and a limit counted in days ahead (how far in advance a guest may book) are all checked when you create the booking, and none of them has a field in the feed. The feed does carry one date limit, restrictions.max_booking_date, the last arrival date sold online; the day-count limit is not in it. Treat the feed as the menu and the create call as the final word, and handle its refusals (see Create a booking).

If your agreement lets you control stop-sell, minimum stay or arrival days, send them with PUT ari/restrictions/ (scope ari:write). The body is a list of rows, one per category and date, or {"restrictions": [...]}:

Terminal window
curl -X PUT https://api.campone.ch/api/v1/public/ari/restrictions/ \
-H "Authorization: Bearer $GRONDIA_API_KEY" \
-H "Content-Type: application/json" \
-d '[
{"site_type_code": "standard", "date": "2027-07-17", "closed_to_arrival": true},
{"site_type_code": "standard", "date": "2027-07-18", "min_stay": 3},
{"site_type_code": "standard", "date": "2027-07-19"}
]'
{ "upserted": 2, "deleted": 1 }
  • A row sets min_stay (0 to 365), closed (stop-sell for that night), closed_to_arrival and closed_to_departure. Anything you leave out is “no restriction”.
  • A row with every knob at its default deletes the restriction for that category and date, as the third row above does. The answer counts the rows written and the rows deleted; a clearing row for a date that has no restriction deletes nothing and is not counted, so sending it again is harmless.
  • An unknown site_type_code answers 400 and nothing is written.
  • GET ari/restrictions/ lists the rows that exist. It returns only dates that carry a restriction, and its end is inclusive, unlike the feed’s.
  • The restrictions are enforced for every booking made through Grondia for that category and night, the campground’s own reception included, not only for yours. Agree with the campground what you may set.

A change shows in the feed at once: a restriction write clears the cached answers.

Push a reservation with POST bookings/ (scope bookings:write). Three things in the body matter for a channel:

  • external_reference: your own reservation id. Keep it under 200 characters, because a longer value is cut. Sending the same reference again returns the booking you created and never makes a second one.
  • channel: a free-text label for your product, up to 64 characters. The campground sees it in its bookings list. booking_engine, in any spelling, is reserved and answers 400.
  • Where to book: either site_id (a pitch from GET sites/) or a category: site_type_code, the code from the feed. With a category, Grondia picks the pitch. A category can also be named by site_type_id or site_type_name (from GET sites/); Grondia tries the id, then the code, then the name, and a name that matches no category exactly is matched by its beginning, so prefer the code. When site_id is sent, the category fields are ignored. check_in_date, check_out_date and num_adults are required; num_youth, num_children and the guest’s name, email, address and nationality are optional.

A few more fields are optional:

  • language: the guest’s language as a two-letter code, such as de or en. The campground’s messages to the guest go out in that language when it has them in that language, and in its default language otherwise.
  • custom_fields: answers to the campground’s own booking-form questions, as an object of question key to answer. The campground gives you its keys; a key it has not set up is dropped without an error.
  • extras: a list of {"code": "...", "quantity": 1}. When you book by category, an extra that needs electricity makes Grondia pick a pitch with power. Extras are not added to the booking or to its price.
  • source is an older name for channel, used only when channel is empty. Send channel.
Terminal window
curl https://api.campone.ch/api/v1/public/bookings/ \
-H "Authorization: Bearer $GRONDIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_reference": "OTA-48213-1",
"channel": "example-channel",
"site_type_code": "standard",
"check_in_date": "2027-07-14",
"check_out_date": "2027-07-17",
"num_adults": 2,
"guest_first_name": "Anna",
"guest_last_name": "Müller",
"guest_email": "anna.mueller@example.com"
}'

A new booking answers 201:

{
"id": 48307,
"booking_reference": "4C8E1B07A2",
"guest_first_name": "Anna",
"guest_last_name": "Müller",
"guest_email": "anna.mueller@example.com",
"site_id": 9071,
"site_number": "A12",
"site_category": "Standard pitch",
"check_in_date": "2027-07-14",
"check_out_date": "2027-07-17",
"num_adults": 2,
"num_youth": 0,
"num_children": 0,
"status": "pending",
"total_price": "151.00",
"created_at": "2026-10-19T09:12:44.104837Z",
"updated_at": "2026-10-19T09:12:44.104837Z",
"external_reference": "OTA-48213-1",
"channel": "example-channel"
}
  • Store id. The list calls do not filter on external_reference, so the id is how you find the booking again for a cancel.
  • total_price is the total Grondia worked out for the stay. Here 151.00 is more than three nights at the feed’s 42.00, because the pitch rate is not the whole bill.
  • site_number is empty when the campground sells a category and does not show pitch numbers; site_category always names the category.
  • The 201 echoes the guest details you sent. A repeat (200) and every read redact them, so do not rely on reading them back.
  • The create call itself sends the guest no email. Your product sends its own confirmation. When the booking is later confirmed, the campground’s own confirmation email can still go out, so agree with the campground who tells the guest what, and send language so that email is in the guest’s language.

Send the same external_reference again and you get 200 and the same booking, whether the first call timed out or you simply retried. Only the key that created the booking gets it back, with guest details redacted. A reference that another key, or a booking not made through this call, already holds answers 409 with {"detail": "external_reference_conflict"}. References are unique per campground, so give yours a prefix of your own.

Two consequences are easy to miss:

  • A repeat never changes the booking. The check looks only at the reference. Sending the same reference with new dates returns the original booking untouched, and the partner API has no call to change the dates or guests of a booking. To amend a reservation, cancel it and create a new one under a new reference.
  • A reference is used up for good. If your first booking was cancelled or released (abandoned, or cancelled), the same reference returns that booking with its final status. It does not make a new one. Always check status in the answer.

Retrying safely covers the other writes and the Idempotency-Key header.

Status Body What it means
201 The booking Created.
200 The booking, guest details redacted You already used this reference. Not a new booking.
400 {"check_in_date": {"code": "min_nights_violation", "message": "..."}} A campground rule refuses the dates. Codes: min_nights_violation, season_min_nights_violation, max_nights_violation, weekly_turnover_violation, min_advance_days_violation, same_day_booking_blocked, booking_cutoff_violation, booking_horizon_exceeded, season_closed.
400 {"detail": "site_not_found"} or {"detail": "site_or_site_type_required"} The site_id is unknown or not for sale online, or you named neither a site nor a category.
400 A code such as MAX_OCCUPANCY under non_field_errors The party is larger than the pitch or the campground allows.
400 {"vehicle_length_m": {"code": "vehicle_length_required", ...}} The campground demands the vehicle’s length (or its weight, vehicle_weight_required). Send vehicle_type, vehicle_length_m and vehicle_weight_kg.
400 {"channel": ["This channel label is reserved."]} You used booking_engine. Sent in source, it is reported on source.
400 {"detail": "create_failed"} Something unexpected failed. Retrying with the same reference is safe. If it persists, write to Felix or Siro.
409 {"detail": "external_reference_conflict"} The reference belongs to someone else.
409 {"detail": "no_pitch_available"}, {"detail": "site_unavailable", ...} or a code such as SITE_UNAVAILABLE or ADMIN_BLOCK under non_field_errors No pitch can be given for these dates, or the pitch is blocked. Pull the feed again and offer other dates.
422 {"detail": "...", "code": "closed_to_arrival", "date": "2027-07-17"} A per-date restriction refuses the stay, and nothing is created. code is the first rule the stay breaks, in this order: stop_sell, closed_to_arrival, min_stay, closed_to_departure. date is the closed night for stop_sell, the departure date for closed_to_departure and the arrival date otherwise.

Branch on code, never on message: the message is written for a guest and may be in German or another language. Errors describes the four shapes an error body comes in. Retrying a 400, 409 or 422 unchanged gets the same answer. Retrying a timeout is safe, because the reference makes the call idempotent.

POST bookings/{id}/cancel/ (scope bookings:write, no body) cancels a booking under the campground’s own cancellation and refund policy, the one a guest’s self-service cancel uses.

Terminal window
curl -X POST https://api.campone.ch/api/v1/public/bookings/48307/cancel/ \
-H "Authorization: Bearer $GRONDIA_API_KEY"
{
"id": 48307,
"booking_reference": "4C8E1B07A2",
"status": "cancelled",
"refund": { "refunded": false, "reason": "no_payment" }
}
  • The refund object says what Grondia refunded. When it holds no payment for the booking, as above, refunded is false with a reason. When it refunds money, refunded is true and the object carries decimal strings such as refund_amount, cancellation_fee and original_amount, among other fields. The set of fields varies with the case, so read only the ones you need.
  • The guest’s cancellation deadline does not apply to you. You may cancel up to the arrival day, but the campground’s cancellation fee still applies to the refund. For an amendment done as cancel plus new booking, agree with the campground how it treats that fee.
  • The fee depends on the days left before arrival, counted on the campground’s calendar. A campground can also set a time of day after which a cancellation counts one day closer to arrival: with 11:00, a cancel at 11:30 on the day before arrival is charged as one on the arrival day.
  • Only reservation_request, pending and confirmed bookings can be cancelled. Any other status answers 400 with {"detail": "not_cancellable", "status": "cancelled"}, naming the current status. Treat that answer to a repeated cancel as done.
  • The call accepts any booking id of the campground, not only yours. Cancel only ids you got from your own creates. An id that does not exist in this campground answers 404.
  • The cancel does not switch off the campground’s own guest emails. The guest can receive the campground’s cancellation message, as with any cancel in its admin. That message states the refund the cancellation decided, and a refund the campground records later can send the guest a short message of its own. Agree with the campground who tells the guest.

Register one endpoint with POST webhooks/ (scope webhooks:write):

Terminal window
curl https://api.campone.ch/api/v1/public/webhooks/ \
-H "Authorization: Bearer $GRONDIA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://channel.example.com/hooks/grondia",
"event_types": ["booking.created", "booking.confirmed", "booking.modified", "booking.cancelled"]
}'

The 201 answer carries the signing secret (it starts with whsec_) once. Store it: no call shows it again. To get a new one, post the same URL again with rotate_secret set to true; posting a URL that is already registered updates it in place. The URL must be https on a public hostname; IP addresses, localhost and private-network hosts are refused, and redirects are not followed.

  • Which events. An endpoint with an empty event_types list gets the nine classic events, which include payment.settled and pitch.status_changed, rarely needed by a channel. Four events arrive only when you name them. Naming the four booking.* events above is the plain choice. booking.booked and booking.changed are the alternative: booking.booked fires for every new booking whatever its first status, booking.changed for every later change or confirmation.
  • Precedence, and a trap. Each change produces one of the classic booking events, picked in this order: cancelled, confirmed, created, modified. A booking that is created already confirmed therefore arrives as booking.confirmed and never as booking.created. A booking you create yourself starts pending, so you see booking.created for it (and, on a campground that takes payment on arrival, booking.confirmed moments later). But the campground’s own bookings, which matter for your availability, can be the other kind. Treat booking.confirmed for an id you have never seen as a new booking.
  • Check status in every payload. An unpaid pending booking is released as abandoned, and that arrives as booking.modified. The rare cancelled variant arrives as booking.cancelled (see A release that arrives as cancelled).
  • Your own writes come back to you. A booking you create or cancel fires its event at your endpoint like any other. Recognise it by external_reference.
  • The payload is small and holds no guest. It carries the booking id, external_reference, booking_reference, status, check_in, check_out, site_type_code, total, currency and updated_at:
{
"event": "booking.confirmed",
"tenant": "beispielsee",
"booking": {
"id": 48307,
"external_reference": "OTA-48213-1",
"booking_reference": "4C8E1B07A2",
"status": "confirmed",
"check_in": "2027-07-14",
"check_out": "2027-07-17",
"site_type_code": "standard",
"total": "151.00",
"currency": "CHF",
"updated_at": "2026-10-19T09:40:12.511903+00:00"
}
}

Each delivery is signed with an X-Grondia-Signature header, so check it before you trust a body (Webhooks shows how, with code); it also carries X-Grondia-Event, X-Grondia-Delivery and X-Grondia-Timestamp. Answer with a 2xx within 10 seconds. A delivery that fails is retried up to five times with growing pauses (Retries). Every event is described in the API reference, for example booking.created, booking.confirmed and booking.cancelled.

Use an event to learn that something changed, then fetch the booking for the details. If you re-pull the feed on an event, remember it can be up to five minutes old.

GET bookings/?updated_since=... returns the bookings changed on or after a moment, oldest change first. Keep the highest updated_at you have processed and send it next time; on the first run use the moment you start syncing (Syncing changes has the details, and says why the sample below starts each request again from the last updated_at instead of following next). Run it every few minutes: it catches a webhook that never arrived, a change made at reception, and a cancellation. It does not see a booking the campground deletes (What neither of them sees).

Terminal window
curl "https://api.campone.ch/api/v1/public/bookings/?updated_since=2026-10-19T09:40:12Z&page_size=200" \
-H "Authorization: Bearer $GRONDIA_API_KEY"
  • The cursor is inclusive. The booking at the cursor comes back once more, so upsert by id and ignore a row whose updated_at you already hold. Start each sweep a few minutes before the cursor you saved (why).
  • It lists the whole campground, not only your bookings: its website bookings and reception bookings too, with guest details redacted. Your own carry your external_reference and channel. Website and reception bookings have both empty, but bookings that came in another way do not: an import of Booking.com reservations carries the channel booking_com and an external_reference that starts with bdc:, and a booking pushed by another partner carries that partner’s. Recognise your own by your prefix, and never treat “has a reference” as “is mine”.
  • status carries the news. A cancelled or released booking shows up with cancelled or abandoned. What a sweep cannot show is in What neither of them sees and Check-ins and check-outs.

Without updated_since the list is newest first by creation, which is not a change feed. An invalid updated_since answers 400.

A pending booking that nobody paid or confirmed is normally released by the five-minute check, as abandoned, and that arrives as booking.modified. There is one other way. If a booking for the same pitch and overlapping dates is created after the 30 minutes are up but before the five-minute check has run, that create releases the old booking itself, as cancelled. That release arrives as booking.cancelled, and it moves updated_at, so the sweep sees it too.

So treat abandoned and cancelled alike for a booking you still held as pending: either way the campground no longer holds the pitch for you.

  • Payment and confirmation agreed with the campground, so your pending bookings are paid, confirmed on arrival or confirmed by reception inside 30 minutes, and your code treats abandoned and cancelled alike as the release of a booking you held as pending.
  • A key with your scopes from Felix or Siro, one key per campground, stored apart from your code.
  • Categories mapped. Each campground’s code values are stored per campground. A legacy category can have an empty code; ask the campground to set one before you sell it.
  • Minimum stay read per category and arrival date, not once per campground.
  • A reference scheme. external_reference is unique per campground, carries a prefix of your own, and a re-booking after a cancel or an amendment gets a new one.
  • Refusals handled by code. A 400, 409 or 422 shows the guest other dates and is never retried unchanged.
  • Webhook endpoint registered, the signature checked, the secret stored (Webhooks).
  • A sweep running every few minutes with a stored cursor (Syncing changes).
  • Request budget worked out against 1000 requests an hour per key, the default (Rate limits). A feed pull every five minutes plus a sweep every five minutes use 24 of them.
  • Guest communication decided: who sends the confirmation and the cancellation notice, you or the campground.
  • A pilot with one campground before you open the channel to the rest. The Partner program describes the review before it and the App Store listing after it.

The sandbox key (see the Quickstart) is read-only, with bookings:read, sites:read, invoices:read and customers:read. With it you can call the two ARI reads and the booking list. You cannot create or cancel a booking, write a restriction or register a webhook: those need bookings:write, ari:write and webhooks:write. To try the whole flow, write to Felix, felix@grondia.com, or to Siro, siro@grondia.com.