Realtyfeed API Token: Fetch, Cache, and Use (Python + cURL)
This guide shows a clean, reusable pattern to obtain a token from https://api.realtyfeed.com/v1/auth/token, cache it until it expires, and automatically use/refresh it for subsequent calls. It also includes minimal "one‑off" examples and a safe way to force‑generate a new token when necessary.
Audience: Python developers and API integrators using Realtyfeed.
Prereqs: Your client_id and client_secret.
TL;DR (Python)
import time
import typing as t
import requests
AUTH_URL = "https://api.realtyfeed.com/v1/auth/token"
class TokenAuth:
"""
Handles fetching and refreshing OAuth-style bearer tokens from Realtyfeed.
Usage:
auth = TokenAuth(client_id="...", client_secret="...")
session = RealtyfeedSession(auth, origin="https://my-app.com")
r = session.get("https://api.realtyfeed.com/reso/odata/Property", params={"$top": 5})
print(r.json())
"""
def __init__(self, client_id: str, client_secret: str, auth_url: str = AUTH_URL, skew: int = 60):
self.client_id = client_id
self.client_secret = client_secret
self.auth_url = auth_url
self._token: t.Optional[str] = None
self._token_type: str = "Bearer"
self._expires_at: float = 0.0
# Refresh a bit early to be safe
self._skew = max(0, skew)
def _fetch(self) -> None:
payload = {
"client_id": self.client_id,
"client_secret": self.client_secret,
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
resp = requests.post(self.auth_url, headers=headers, data=payload, timeout=20)
resp.raise_for_status()
data = resp.json()
# Expected payload: {"access_token":"...","expires_in":86400,"token_type":"Bearer"}
self._token = data["access_token"]
self._token_type = data.get("token_type", "Bearer")
expires_in = int(data.get("expires_in", 0))
# now + lifetime - skew
self._expires_at = time.time() + max(0, expires_in - self._skew)
def token(self) -> str:
if not self._token or time.time() >= self._expires_at:
self._fetch()
return self._token # type: ignore[return-value]
def auth_header(self) -> str:
return f"{self._token_type} {self.token()}"
class RealtyfeedSession(requests.Session):
"""
requests.Session that injects Authorization and Origin headers.
Auto-refreshes token and retries once on 401/403.
"""
def __init__(self, auth: TokenAuth, origin: t.Optional[str] = None):
super().__init__()
self.auth = auth
self.origin = origin
def _headers(self, headers: t.Optional[dict] = None) -> dict:
h = {} if headers is None else dict(headers)
h.setdefault("Authorization", self.auth.auth_header())
if self.origin:
h.setdefault("Origin", self.origin)
return h
def request(self, method, url, **kwargs):
# First attempt with current/valid token
kwargs["headers"] = self._headers(kwargs.get("headers"))
resp = super().request(method, url, **kwargs)
# If unauthorized/forbidden, refresh token and retry once
if resp.status_code in (401, 403):
self.auth._fetch() # force refresh
kwargs["headers"] = self._headers(kwargs.get("headers"))
resp = super().request(method, url, **kwargs)
return resp
if __name__ == "__main__":
CLIENT_ID = "YOUR_CLIENT_ID"
CLIENT_SECRET = "YOUR_CLIENT_SECRET"
auth = TokenAuth(client_id=CLIENT_ID, client_secret=CLIENT_SECRET)
session = RealtyfeedSession(auth, origin="https://my-app.com")
url = "https://api.realtyfeed.com/reso/odata/Property"
r = session.get(url, params={"$top": 5}, timeout=30)
r.raise_for_status()
print(r.json())
Minimal Token Fetch (One‑Off)
import requests
url = "https://api.realtyfeed.com/v1/auth/token"
payload = {"client_id": "YOUR_CLIENT_ID", "client_secret": "YOUR_CLIENT_SECRET"}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = requests.post(url, headers=headers, data=payload, timeout=20)
response.raise_for_status()
data = response.json()
print(data) # {'access_token': '...', 'expires_in': 86400, 'token_type': 'Bearer'}
Use the Token Manually in a Follow‑Up Request
import requests
# from previous step
access_token = data["access_token"]
token_type = data.get("token_type", "Bearer")
headers = {
"Authorization": f"{token_type} {access_token}",
"Origin": "https://my-app.com",
}
r = requests.get("https://api.realtyfeed.com/reso/odata/Property", headers=headers, timeout=30)
r.raise_for_status()
print(r.json())
cURL Examples
Fetch Token
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
https://api.realtyfeed.com/v1/auth/token
Call OData with Token
TOKEN="<paste access_token here>"
TYPE="Bearer" # or use the returned token_type
curl -X GET \
-H "Authorization: $TYPE $TOKEN" \
-H "Origin: https://my-app.com" \
"https://api.realtyfeed.com/reso/odata/Property?$top=5"
Forcing a New Token (When Necessary)
A token (or API key) can be restricted by origin (domain) and/or IP address to enhance security. If you configure restrictions in API Key Settings, every request must comply with those rules.
If no restrictions are set, the key is valid from any origin or IP.
If restrictions are defined, every request must match the configured allowed origins and/or allowed IPs.
❌ Non‑compliant requests (origin/IP mismatch):
The API returns 403 Forbidden.
The token used in that request is blocked for one hour.
While blocked, that token cannot be used for any requests. After one hour, it becomes valid again, but will be blocked again if requests are still non‑compliant.
⚠️ If a token is blocked repeatedly, rotate it or request a new token via the authentication endpoint.
See: API Key Settings (internal Confluence) for configuring allowed origins and IPs.
If your token is blocked (403) or compromised, you may request a brand‑new token by passing the query parameter force_new_token=true to the token endpoint. This is rate‑limited (max 50 per day) and resets daily at 00:00 UTC.
Python
import requests
url = "https://api.realtyfeed.com/v1/auth/token"
params = {"force_new_token": "true"} # query param
payload = {
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
resp = requests.post(url, params=params, headers=headers, data=payload, timeout=20)
resp.raise_for_status()
print(resp.json())
# {
# "access_token": "...",
# "expires_in": 86400,
# "token_type": "Bearer",
# "force_new_token_info": {
# "force_new_token_count": 2,
# "force_new_token_count_limit": 50,
# "force_new_token_count_reset_info": "force_new_token_count will be reset daily at 00:00 UTC"
# }
# }
cURL
curl -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
"https://api.realtyfeed.com/v1/auth/token?force_new_token=true"
Best Practice: Prefer the default
/auth/tokenflow. Only useforce_new_token=true(as a query parameter) for 403‑blocked or compromised tokens.
Behavior & Defaults
Token lifetime:
expires_inis usually86400(24 hours).Auto‑refresh window: In the
TokenAuthsample, refresh is attempted ~60 seconds before expiry (configurable viaskew).Auto‑retry: The custom
RealtyfeedSessionretries once on401/403after a forced refresh.Required headers:
Authorization: Bearer <token>and, where applicable,Origin: https://your-site.example.
Security Notes
Do not commit
client_id/client_secretto source control.Load secrets with environment variables or a secrets manager (e.g., AWS Secrets Manager, Vault, Doppler).
Prefer short timeouts and fail‑fast retry policies at the call‑site.
Log token metadata only (never the raw token).
Troubleshooting
401 Unauthorized: Token missing/invalid. Ensure
AuthorizationisBearer <token>and the token hasn’t expired.403 Forbidden: Token blocked or insufficient scope. If revived attempts fail, consider
force_new_token=true(respect the daily limit).Clock skew: If servers’ time is off, use a larger
skew(e.g., 120–300 seconds) or sync time (NTP).CORS/Origin: Provide a valid
Originheader when required by the endpoint.Timeouts: Increase client timeouts slightly or check network egress/firewall rules.
Appendix: Packaging Tips
Wrap
TokenAuthandRealtyfeedSessionin a small internal package (e.g.,realtyfeed_auth) and reuse across services.Add unit tests that:
mock the token endpoint,
assert early refresh behavior,
verify retry‑on‑401/403 logic.