I created my own application using python, mysql, graphql, and cronjobs to pull Upwork data, store in DB, then generate RSS feed to display what I want. I fire off a cronjob to automatically refresh the token and will even send me an email if it fails for any reason. Let me know if I can help.
#!/usr/bin/env python3
import os
import json
from datetime import datetime, timedelta
from requests_oauthlib import OAuth2Session
import smtplib
from email.mime.text import MIMEText
from pathlib import Path
import tempfile
# ----- Paths (env overrides -> secure defaults) -----
CONFIG_PATH = Path(os.getenv("CONFIG_PATH", "/var/secure/upwork_feed_secrets/config.json"))
TOKEN_PATH = Path(os.getenv("TOKEN_PATH", "/var/secure/upwork_feed_secrets/token.json"))
ERROR_LOG_PATH = Path("/tmp/upwork_token_error.log") # temp file to track last alert date
# ----- Helpers -----
def load_json(path: Path):
if path.exists():
with path.open("r") as f:
return json.load(f)
return None
def save_json_atomic(path: Path, data: dict):
# write to a temp file then move into place
with tempfile.NamedTemporaryFile("w", delete=False, dir=str(path.parent)) as tmp:
json.dump(data, tmp, indent=2)
tmp.flush()
os.fsync(tmp.fileno())
tmp_name = tmp.name
os.replace(tmp_name, path)
def now_ts():
return datetime.now()
def parse_port(port_value, default=587):
try:
return int(port_value)
except Exception:
return default
def get_smtp_config(cfg: dict):
"""
Support either:
cfg["smtp2go"] = {username, password, smtp_server, smtp_port, (optional) from, to}
cfg["smtp"] = {username, password, host, port, from, to}
Returns normalized dict or None.
"""
smtp = cfg.get("smtp")
s2g = cfg.get("smtp2go")
if smtp:
return {
"host": smtp.get("host"),
"port": parse_port(smtp.get("port", 587)),
"username": smtp.get("username"),
"password": smtp.get("password"),
"from": smtp.get("from") or smtp.get("username"),
"to": smtp.get("to"),
}
if s2g:
return {
"host": s2g.get("smtp_server"),
"port": parse_port(s2g.get("smtp_port", 587)),
"username": s2g.get("username"),
"password": s2g.get("password"),
"from": s2g.get("from") or s2g.get("username"),
"to": s2g.get("to") or "[email protected]", # default you used in original
}
return None
def should_send_email():
if ERROR_LOG_PATH.exists():
try:
with ERROR_LOG_PATH.open("r") as f:
last_sent = datetime.strptime(f.read().strip(), "%Y-%m-%d")
return last_sent.date() < now_ts().date()
except Exception:
return True
return True
def mark_email_sent():
with ERROR_LOG_PATH.open("w") as f:
f.write(now_ts().strftime("%Y-%m-%d"))
def send_alert_email(smtp_cfg: dict | None, error_msg: str):
if not smtp_cfg:
# silently skip if no SMTP configured
print("[WARN] SMTP not configured; skipping alert.")
return
try:
msg = MIMEText(f"❌ Upwork token refresh failed:\n\n{error_msg}")
msg["Subject"] = "Upwork Token Refresh Failed"
msg["From"] = smtp_cfg["from"]
msg["To"] = smtp_cfg["to"]
with smtplib.SMTP(smtp_cfg["host"], smtp_cfg["port"]) as server:
server.starttls()
server.login(smtp_cfg["username"], smtp_cfg["password"])
server.sendmail(smtp_cfg["from"], [smtp_cfg["to"]], msg.as_string())
print("🚨 Alert email sent.")
except Exception as e:
print(f"[WARN] Failed to send email: {str(e)}")
def need_refresh(token: dict) -> bool:
"""
If expires_at exists: refresh when we're within 60s of expiry.
If not present: play it safe and refresh.
"""
try:
exp_at = token.get("expires_at")
if exp_at is None:
return True
expires_at = datetime.fromtimestamp(exp_at)
return now_ts() >= (expires_at - timedelta(seconds=60))
except Exception:
return True
def main():
# Load config
cfg = load_json(CONFIG_PATH)
if not cfg:
print(f"[ERROR] No config found at {CONFIG_PATH}")
return
# Upwork creds (your current keys)
upwork = cfg.get("upwork") or {}
client_id = upwork.get("api_key") or upwork.get("client_id")
client_secret = upwork.get("api_secret") or upwork.get("client_secret")
redirect_uri = upwork.get("redirect_uri")
if not client_id or not client_secret:
msg = "Missing upwork.api_key/api_secret (or client_id/client_secret) in config.json"
print(f"[ERROR] {msg}")
if should_send_email():
send_alert_email(get_smtp_config(cfg), msg)
mark_email_sent()
return
# SMTP config (optional)
smtp_cfg = get_smtp_config(cfg)
# Load token
token = load_json(TOKEN_PATH)
if not token:
print("[ERROR] No token.json found.")
if should_send_email():
send_alert_email(smtp_cfg, "token.json missing")
mark_email_sent()
return
# Prepare OAuth session
token_url = "https://www.upwork.com/api/v3/oauth2/token"
try:
if need_refresh(token):
print("🔄 Token needs refresh. Refreshing...")
# Upwork expects client_id & client_secret in the body for refresh
oauth = OAuth2Session(client_id, token=token, redirect_uri=redirect_uri,
auto_refresh_url=token_url, auto_refresh_kwargs={
"client_id": client_id,
"client_secret": client_secret,
}, token_updater=lambda t: None) # we save manually below
# Explicit call to refresh endpoint
new_token = oauth.refresh_token(
token_url,
client_id=client_id,
client_secret=client_secret,
)
# Persist atomically
save_json_atomic(TOKEN_PATH, new_token)
print("✅ Token refreshed and saved.")
else:
print("✅ Token is still valid.")
except Exception as e:
err = str(e)
print(f"❌ Token refresh failed: {err}")
if should_send_email():
send_alert_email(smtp_cfg, err)
mark_email_sent()
if __name__ == "__main__":
main()