First steps

NOTE: BostadsAPI is for publishing content on Hemnet. We do not offer a public API for extracting data.

  1. Contact [email protected] to request access to the test environment.
  2. Once you have received credentials, log in and create a key for your development application. Note that once you have a key, you can manage keys via API calls.
  3. Start building your application.
  4. Note that published listings are publicly available on the web.

Authentication

In order to get your first authentication tokens you need to log in to the API
portal. Here you can create a key, which will show you the secret token once.
Save this somewhere safe and provide your application with it.

A key has a randomized ID, which is returned along with the token when a Key is
created.

NOTE: Currently an auth token consists of <ID>.<SECRET>. This format is
not guaranteed to remain and the secret token should be regarded as an opaque
string by all systems.

Keys expire after some time, so it is important to make sure you can change the
key easily at runtime.

Providing secret tokens to API requests

Place the secret token inside the Authorization header. If your secret token is
84Qx3dBR7CMV9p-1HsvA.v2KqPYU2YVKozxxdg152, then the header should look like this:

Authorization: Bearer 84Qx3dBR7CMV9p-1HsvA.v2KqPYU2YVKozxxdg152

Testing authentication

A simple way to test your authentication is to access the GET /keys route,
which should always give a fast non-empty result back if you are authenticated.

Automatically dealing with expiry

There is an API to create new keys that you can use to automatically deal with
key renewal without human intervention.

In the following examples, each customer in the system has their own API key
for auditing and security but this is up to you if you want to use this method
or not.

Start by checking the current key’s status by fetching the Key details:

GET /keys/4Y0BP-mbkuv40_44aJfG
{
  "key": {
    "id": "4Y0BP-mbkuv40_44aJfG",
    "name": "Prod 2017:Q3 - Customer 45",
    "expiresAt": "2017-12-31T00:00:00+02:00",
    "lastUsedAt": "2017-10-13T14:32:11+02:00",
    "createdBy": {},
    "state": "active"
  }
}

Here you can see that the key is active, but it will expire at 2017-12-31. If
this date is close you can create a new key:

POST /keys
{
  "name": "Prod 2017:Q4 - Customer 45",
}
{
  "authToken": "84Qx3dBR7CMV9p-1HsvA.v2KqPYU2YVKozxxdg152",
  "key": {
    "id": "84Qx3dBR7CMV9p-1HsvA",
    "name": "Prod 2017:Q4 - Customer 45",
    "expiresAt": "2018-07-07T00:00:00+02:00",
    "lastUsedAt": null,
    "createdBy": {
      "type": "key",
      "name": "Prod 2017:Q3 - Customer 45",
      "identifier": "4Y0BP-mbkuv40_44aJfG"
    },
    "state": "active"
  }
}

Store the new Key’s ID and auth token in your database.

customer.change_key(created_key.key.id, created_key.auth_token)

Now use the new key for the next request. If you are sure nothing is using the
old key you can revoke it manually, or wait for it to expire.

schedule_key_revoke_job(2.minutes, old_key.id)
DELETE /keys/4Y0BP-mbkuv40_44aJfG

Error handling

Most errors should be declared in the API specification, but there are always
the possibility of unexpected errors to prop up. There are also a few errors
that are considered “global”, like for example not being authenticated.

Errors should all look like this:

{
  "errors": [
    {
      "type": "ErrorType",
      "message": "ErrorMessage",
      "extraFieldsDependingOnErrorType": "..."
    }
  ]
}

That is, the document should only have a single key called errors which
contains an array of errors.
All errors will have a type and a message. Depending on the type more
fields could be present.

You can parse them into structured error types on your side.

Generic errors

When no more specific error type is appropriate, an error will become a
GenericError. They do not have any extra fields.

{
  "errors": [
    {
      "type": "GenericError",
      "message": "Persistence failed because of cosmic rays. Try again!"
    }
  ]
}

Validation errors

Validation errors will be emitted as an ValidationError type and have the
following extra fields:

  • field – The name of the field with the validation error.
  • fieldMessage – An error message that is scoped to the field. See the
    example below.
{
  "errors": [
    {
      "type": "ValidationError",
      "message": "urls.allImages is not a valid URL",
      "field": "urls.allImages",
      "fieldMessage": "is not a valid URL"
    }
  ]
}

The intention of field is that it should exactly match the name of the field
used in the request (using dot-notation for nested fields). If they do not
match, that is a bug and we’d love it if you could report it to us.

fieldMessage is supposed to be a message you could attach to the underlying
input field or show in contexts where the problem is obvious. message is
instead the full message that can be logged or otherwise shown to a developer
as it contains the field name that is used in the API (which might not be
a user-friendly name).

In the example above, “is not a valid URL” could be shown in a error message
right under the input field for the URL. “urls.allImages is not a valid URL”
would not be appropriate to show there.

Showing messages in Swedish

We’d love to hear from you if you’d like to have the error messages translated
to Swedish so we can find a solution that covers your needs.

There are many ways of representing errors and not all of them might be
appropriate for all cases. Real-world examples and someone to talk to would
help us immensely.

For now you could opt to just show “There is a problem with this input” to the
user in their language and hope that they understand what the error is. In the
more complicated cases you could replicate our validation logic on your side to
give users quicker feedback, but that is only valid for more obvious types like
email addresses, URLs, numbers, and so on.

When to publish listings

The new API is built to support a workflow where a listing is updated in small
steps until the user is happy with the data. Then publication happens as a
separate request without also making changes to it.

This allows you to wait to publish until all images are completed, or to show
other feedback to users regarding information that is recommended to add before
publishing.

Most users will be on contracts with Hemnet where each individual listing needs
to be approved in Kundportalen before it can be published. This is something
that needs to be done before the publication if you want to completely control
the time of publication using the API.

Thankfully the API can tell you if the listing is approved or not, which is
also something you can show your users.

Some users are on contracts that allow auto-publishing without explicit
approvals. In those cases the listing will appear to be approved almost
immediately after the listing is created without the user doing anything.

NOTE: Currently approving in Kundportalen will also publish the listing
automatically without an API call. This will change in the near future to
require action at both ends.

Unpublishing and marking as sold

If the user do not want the listing to show up on Hemnet anymore they can
unpublish just like before.

However, if the listing is supposed to be unpublished because it has been sold,
then the listing should not be unpublished. Instead it should be marked as
sold. Doing this ensures Hemnet will be able to correctly treat the listing
lifecycle, which helps with subscriptions, metrics and tracking turnarounds.

Partial updates

When updating a record (using the PATCH verb) you do not have to send the
complete record. Send only the keys you want to
change and any keys that are left out will be unchanged.

As an example, updating a Showing with the following document will only change
the end date:

PATCH /listings/:listing_id/showings/:showing_id
{
  "endAt": "2018-07-07T12:00:00+02:00"
}

If you include a key with the value null it will be unset, when possible. In
this example, the end time of the showing is instead removed:

{
  "endAt": null
}

Sending partial changes like this ensures that the change will happen faster
and also makes it possible to make several update requests at the same time
that still applies safely.

It is important that your internal models allow updates to differentiate
between “no change” and “null”. There are many ways of modelling this, from keeping a separate list of which fields to pass to
the request, or using purely dynamic types.

// Keeping list of attributes to send (Java)
class ShowingUpdateBuilder {
  public ShowingUpdateBuilder changeEndAt(value: DateTime) {
    this.changedFields.add("endAt");
    this.endAt = value;
    return this;
  }

  public ShowingUpdateBuilder unsetEndAt() {
    this.changedFields.add("endAt");
    this.endAt = null;
    return this;
  }

  public ShowingUpdateBuilder forgetChangeToEndAt() {
    this.changedFields.remove("endAt");
    this.endAt = null;
    return this;
  }
}
# Purely dynamic type, using a Hash/Map/Dictionary (Ruby)
class ShowingUpdate
  def initialize
    @updates = Hash.new
  end

  def end_at=(value)
    @updates["endAt"] = value
  end

  def forget_end_at
    @updates.delete("endAt")
  end

  def to_json(*args)
    @updates.to_json(*args)
  end
end

There are, of course, other ways of doing this, but two examples might be
enough for you to find a solution that fits with your current data models.

Code generation

The new API also supports code generation since it is based on the Swagger
specification (now Open API). When reading the API
documentation
, add a .json extension to the URL to get the Swagger
specification for the API. This JSON can then be fed to a code generator for
Swagger
.

Lifecycle of a listing

Although many operations are available to you, below is a breakdown of the most common API actions, a workflow followed by most listings.

Establish a Broker Agency

Even if your integration will be used by a single Broker Agency, you need to create that first Broker Agency, or fetch the ID of one you’ve created previously.

Create a Broker Agency

List your Broker Agencies

Create a new listing

The endpoint for creating a new listing accept a long range of parameters, but many are optional. You will need to provide some basic info in order to be able to create a listing that can be published. Historically, the more informations that is provided for a listing, the better it tends to perform on the market. In the future, data completeness may also influence relevance in search results.

Create a listing

Add images

In order to be able to publish a listing, it needs at least one image. When you add an image, you provide a url at which the image can be fetched – not the data payload of the image. This means that the image you want to add must be accessible from Hemnet server IPs.

Create a listing image

Wait for images to be processed

Once you’ve created images, poll the API to find out when images have been fetched by Hemnet’s image processor. Remember to set the ‘limit’ property to include all your images if you’ve added more than 20. The status for each image is reported on the ‘state’ property, and once it changes from ‘in_progress’ to ‘completed’ you’re good to go. If an image fails to be processed, more details as to why available if you fetch the individual record (there’s even a human readable reason called ‘stateMessage’ that you can display to end users if needed).

List images belonging to a listing

Publish the listing

Publishing is a two part process, where the listing will become visible and searchable once it has been published and approved.

Publish a listing

Approve the listing

Approving is a manual step done by the Broker via Kundportalen. (The approval address is a available on the details of each listing.) This step is intended to make sure the Broker validates seller invoice information and that the seller has signed the Hemnet publication terms. Approval can happen before or after the listing is published.

Verify that the listing is visible on Hemnet

The response from the Publish action contains the property “isVisible”, and when it is ‘true’, the listing is visible on hemnet and available in searches. This property is also available when you fetch an individual listing and on enumeration of listings.

Mark the listing as sold

Once the listing is sold, you’ll mark it as sold. This automatically unpublishes the listing, and should always be used after a successful sale, and helps Brokers collect valuable leads and increases Broker exposure over time, as well as helping users understand that the listing is unavailable due to being sold.

You should also report the sale price at this time. The sale price should be reported as public or private, depending on the wishes of the parties involved in the transaction.

Mark a listing as sold

Full API documentation

You can read the full API documentation by visiting the API endpoint.