Partner API Integration Guide — Shortlet
List approved Shortlet properties, show their details on your platform, and securely hand a verified customer over to Shortlet to complete a reservation.
Integration overview
The Shortlet Partner API lets an approved partner:
- Retrieve approved Shortlet properties.
- Display property details on the partner platform.
- Securely hand a partner customer over to Shortlet to complete a reservation.
The three endpoints
| Method | Endpoint |
|---|---|
| GET | /v1/api/partner/properties |
| GET | /v1/api/partner/properties/:id |
| POST | /v1/api/partner/handoffs/reservation |
What Shortlet stays responsible for
- Property availability
- Customer creation or login on Shortlet
- Reservation dates and guest selection
- Booking creation
- Payment
- Booking lifecycle
Partner credentials
After Shortlet approves your partner application, log in to Shortlet and open the partner dashboard. From the dashboard, copy your API key and handoff secret.
API key format
shk_live_<8_hex_prefix>_<32_hex_secret>
# Example
shk_live_a1b2c3d4_0123456789abcdef0123456789abcdefCredential responsibilities
| Credential | Purpose |
|---|---|
| API key | Authenticates your backend and identifies your partner account |
| Handoff secret | Signs reservation handoff data to prevent tampering |
- Store both credentials only on your backend.
- Never embed them in frontend JavaScript or mobile app code.
- Never send the handoff secret to the customer's browser.
- Do not commit credentials to source control.
- Use separate API keys for development and production where available.
- If an API key is exposed, revoke it from the partner dashboard and generate a new one.
API base URL
Use the Shortlet staging API base URL:
https://api-staging.shortlet.appAuthentication header
All three endpoints require the API key in the X-API-Key header.
X-API-Key: shk_live_<8_hex_prefix>_<32_hex_secret>
# Example
curl "https://api-staging.shortlet.app/v1/api/partner/properties" \
-H "X-API-Key: {{PARTNER_API_KEY}}"List approved properties
Returns approved Shortlet properties that may be displayed on the partner platform.
Pagination
| Parameter | Type | Description |
|---|---|---|
| page | number | Page to retrieve. Defaults to page 1 |
| limit | number | Number of properties per page |
| sort | string | Sort expression, for example -createdAt or amount |
GET /v1/api/partner/properties?page=2&limit=20&sort=-createdAtFilters
| Parameter | Type | Example |
|---|---|---|
| city | string | Lagos |
| state | string | Lagos |
| country | string | Nigeria |
| type | string | Apartment |
| minPrice | number | 50000 |
| maxPrice | number | 250000 |
| guests | number | 2 |
| bedrooms | number | 2 |
| beds | number | 2 |
| bathrooms | number | 1 |
| from | ISO date | 2026-07-10 |
| to | ISO date | 2026-07-13 |
Example requests
GET /v1/api/partner/properties?type=Apartment
GET /v1/api/partner/properties?city=Lagos&page=1&limit=20
GET /v1/api/partner/properties?country=Nigeria&state=Lagos&type=ApartmentExample curl request
curl --get "https://api-staging.shortlet.app/v1/api/partner/properties" \
-H "X-API-Key: {{PARTNER_API_KEY}}" \
--data-urlencode "country=Nigeria" \
--data-urlencode "city=Lagos" \
--data-urlencode "type=Apartment" \
--data-urlencode "page=1" \
--data-urlencode "limit=20"Example response
{
"status": true,
"statusCode": 200,
"message": "Properties retrieved successfully.",
"data": {
"result": [
{
"id": "698d89406f2b670830021be3",
"name": "Ocean View Apartment",
"slug": "ocean-view-apartment",
"type": "Apartment",
"location": {
"city": "Lagos",
"state": "Lagos",
"country": "Nigeria",
"address": "Victoria Island",
"coordinates": {
"type": "Point",
"coordinates": [3.421, 6.428]
}
},
"amount": 150000,
"cautionFee": 25000,
"minimumDays": 1,
"photos": ["https://example.com/property.jpg"],
"mainImage": "https://example.com/property.jpg",
"amenities": [{ "id": "amenity-id", "name": "Wi-Fi", "slug": "wi-fi" }],
"description": {
"guests": 2,
"bedrooms": 1,
"beds": 1,
"bathrooms": 1
},
"ratingsAverage": 4.5,
"ratingsQuantity": 20,
"discounts": [],
"bookedDates": []
}
],
"count": 1
},
"jwt": null
}Fetch one property
Returns the details of one approved Shortlet property.
Example
curl "https://api-staging.shortlet.app/v1/api/partner/properties/698d89406f2b670830021be3" \
-H "X-API-Key: {{PARTNER_API_KEY}}"Response
{
"status": true,
"statusCode": 200,
"message": "Property retrieved successfully.",
"data": {
"id": "698d89406f2b670830021be3",
"name": "Ocean View Apartment",
"type": "Apartment",
"location": {
"city": "Lagos",
"state": "Lagos",
"country": "Nigeria"
},
"amount": 150000,
"photos": [],
"amenities": [],
"bookedDates": []
},
"jwt": null
}Not found
{
"status": false,
"statusCode": 404,
"message": "Property was not found."
}Reservation handoff
When a customer clicks Reserve Apartment on the partner platform, your frontend should call your own backend. Your backend must generate an HMAC-SHA256 package and transfer payload configurations directly to Shortlet securely.
Request body
{
"propertyId": "698d89406f2b670830021be3",
"uniqueUserId": "customer-12345",
"timestamp": 1782230400,
"nonce": "6cf95610-bb34-45c0-a838-f9e3219fd741",
"shortletDigest": "64_character_lowercase_hex_digest",
"userBio": {
"name": "Jane Doe",
"email": "[email protected]",
"phone": "+2348012345678",
"isKycVerified": true
}
}Field reference
| Field | Required | Description |
|---|---|---|
| propertyId | Yes | Shortlet property ID returned by the property endpoints |
| uniqueUserId | Yes | Stable customer identifier from your system |
| timestamp | Yes | Current Unix timestamp in seconds |
| nonce | Yes | Unique random value for this request |
| shortletDigest | Yes | HMAC-SHA256 digest encoded as lowercase hexadecimal |
| userBio.name | Yes | Customer's first and last name |
| userBio.email | Yes | Customer email address |
| userBio.phone | Yes | Customer phone number, preferably international format |
| userBio.isKycVerified | Yes | Whether your platform has verified the customer |
HMAC signature
Canonical string
propertyId=698d89406f2b670830021be3
uniqueUserId=customer-12345
timestamp=1782230400
nonce=6cf95610-bb34-45c0-a838-f9e3219fd741
name=Jane Doe
[email protected]
phone=+2348012345678
isKycVerified=trueNode.js
import { createHmac, randomUUID } from 'node:crypto';
function buildHandoffPayload({ propertyId, customer, handoffSecret }) {
const timestamp = Math.floor(Date.now() / 1000);
const nonce = randomUUID();
const payload = {
propertyId,
uniqueUserId: customer.id,
timestamp,
nonce,
userBio: {
name: customer.name,
email: customer.email,
phone: customer.phone,
isKycVerified: customer.isKycVerified,
},
};
const canonicalString = [
`propertyId=${payload.propertyId}`,
`uniqueUserId=${payload.uniqueUserId}`,
`timestamp=${payload.timestamp}`,
`nonce=${payload.nonce}`,
`name=${payload.userBio.name}`,
`email=${payload.userBio.email}`,
`phone=${payload.userBio.phone}`,
`isKycVerified=${payload.userBio.isKycVerified}`,
].join('\n');
const shortletDigest = createHmac('sha256', handoffSecret).update(canonicalString, 'utf8').digest('hex');
return { ...payload, shortletDigest };
}Python
import hashlib
import hmac
import time
import uuid
def build_handoff_payload(property_id, customer, handoff_secret):
timestamp = int(time.time())
nonce = str(uuid.uuid4())
user_bio = {
"name": customer["name"],
"email": customer["email"],
"phone": customer["phone"],
"isKycVerified": customer["isKycVerified"],
}
canonical_string = "\n".join([
f"propertyId={property_id}",
f"uniqueUserId={customer['id']}",
f"timestamp={timestamp}",
f"nonce={nonce}",
f"name={user_bio['name']}",
f"email={user_bio['email']}",
f"phone={user_bio['phone']}",
f"isKycVerified={str(user_bio['isKycVerified']).lower()}",
])
shortlet_digest = hmac.new(
handoff_secret.encode("utf-8"),
canonical_string.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return {
"propertyId": property_id,
"uniqueUserId": customer["id"],
"timestamp": timestamp,
"nonce": nonce,
"shortletDigest": shortlet_digest,
"userBio": user_bio,
}PHP
<?php
$canonicalString = implode("\n", [
"propertyId={$payload['propertyId']}",
"uniqueUserId={$payload['uniqueUserId']}",
"timestamp={$payload['timestamp']}",
"nonce={$payload['nonce']}",
"name={$payload['userBio']['name']}",
"email={$payload['userBio']['email']}",
"phone={$payload['userBio']['phone']}",
"isKycVerified=" . ($payload['userBio']['isKycVerified'] ? 'true' : 'false'),
]);
$payload['shortletDigest'] = hash_hmac(
'sha256',
$canonicalString,
$handoffSecret
);Submit the handoff
Node.js fetch example
const payload = buildHandoffPayload({
propertyId,
customer,
handoffSecret: process.env.SHORTLET_HANDOFF_SECRET,
});
const response = await fetch(`${process.env.SHORTLET_API_BASE_URL}/v1/api/partner/handoffs/reservation`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.SHORTLET_PARTNER_API_KEY,
},
body: JSON.stringify(payload),
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.message || 'Unable to create Shortlet handoff');
}
return result.data.redirectUrl;Example response
{
"status": true,
"statusCode": 201,
"message": "Reservation handoff created successfully.",
"data": {
"token": "4e7b462e8732b10bbbdf3532e3a946d37994a2ff234093c0aa1a08cdd2c47b7c",
"redirectUrl": "https://shortlet.app/listings/698d89406f2b670830021be3/?pageView=live&partner=true&token=4e7b462e8732b10bbbdf3532e3a946d37994a2ff234093c0aa1a08cdd2c47b7c",
"expiresAt": "2026-06-23T12:10:00.000Z"
},
"jwt": null
}Error handling
Missing API key
{ "statusCode": 401, "message": "Missing API key." }Invalid API key
{ "statusCode": 401, "message": "Invalid API key." }Revoked or expired API key
{ "statusCode": 401, "message": "API key has been revoked." }
{ "statusCode": 401, "message": "API key has expired." }Invalid signature
{ "statusCode": 401, "message": "Invalid reservation handoff digest." }Recommended environment variables
SHORTLET_API_BASE_URL=https://api-staging.shortlet.app
SHORTLET_PARTNER_API_KEY=shk_live_...
SHORTLET_HANDOFF_SECRET=...