1<!DOCTYPE html><html lang="en"><head> 2 <!-- Analytics will be loaded after DOM ready for better performance --> 3 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> 6
6<script async="" src="https://www.googletagmanager.com/gtm.js?id=GTM-TZZ5448V"></script>
6<script type="text/javascript" async="" src="https://www.clarity.ms/tag/txssedjw3f?ref=gtm"></script>
6<script type="text/javascript" async="" src="https://www.googletagmanager.com/gtag/js?id=G-VXZQGNBXN9&cx=c&gtm=4e69u2"></script>
6<script async="" src="https://www.googletagmanager.com/gtm.js?id=GTM-TZZ5448V"></script>
6<script type="text/javascript" async="" src="//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js"></script>
6<script> 7 (function () { 8 var protocol = window.location && window.location.protocol; 9 var isNativeShell = protocol === "capacitor:" || protocol === "ionic:"; 10 11 if (!isNativeShell) { 12 return; 13 } 14 15 document.documentElement.classList.add("native-capacitor-shell"); 16 17 function clearNativeStartupRoot() { 18 var root = document.getElementById("root"); 19 if (!root) { 20 return false; 21 } 22 23 var hasPrerenderedContent = 24 root.getAttribute("data-prerendered") === "true"; 25 var hasStartupLoader = !!root.querySelector("#loading-indicator"); 26 27 if (!hasPrerenderedContent && !hasStartupLoader) { 28 return false; 29 } 30 31 root.innerHTML = ""; 32 root.removeAttribute("data-prerendered"); 33 root.removeAttribute("style"); 34 root.setAttribute("data-native-startup-cleared", "true"); 35 return true; 36 } 37 38 if (!clearNativeStartupRoot()) { 39 var observer = new MutationObserver(function () { 40 if (clearNativeStartupRoot()) { 41 observer.disconnect(); 42 } 43 }); 44 45 observer.observe(document.documentElement, { 46 childList: true, 47 subtree: true, 48 }); 49 50 document.addEventListener( 51 "DOMContentLoaded", 52 function () { 53 clearNativeStartupRoot(); 54 observer.disconnect(); 55 }, 56 { once: true } 57 ); 58 } 59 })(); 60 </script>
60 61 <style> 62 html.native-capacitor-shell #root[data-prerendered="true"] { 63 visibility: hidden !important; 64 opacity: 0 !important; 65 } 66 </style> 67 68 <!-- Safari Compatibility Meta Tags --> 69 <meta name="format-detection" content="telephone=no"> 70 <meta name="apple-mobile-web-app-capable" content="yes"> 71 <meta name="apple-mobile-web-app-status-bar-style" content="default"> 72 <meta name="apple-touch-fullscreen" content="yes"> 73 74 <!-- Additional Safari iOS Support --> 75 <meta name="mobile-web-app-capable" content="yes"> 76 <meta name="apple-mobile-web-app-title" content="REPSShield"> 77 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> 78 <!-- SEO Meta Tags --> 79 <meta name="description" content="Professional IRS REP tracking software to track REP hours and maintain IRS compliance. Comprehensive real estate professional time tracking with audit-ready reports for REP status qualification."> 80 <meta name="keywords" content="IRS REP tracking, track REP hours, IRS compliance tools, real estate professional, REP status, time tracking, material participation, audit compliance"> 81 <meta name="author" content="REPSShield"> 82 <meta name="robots" content="index, follow"> 83 84 <!-- Open Graph Meta Tags --> 85 <meta property="og:title" content="IRS REP Tracking & Compliance Tools | REPSShield"> 86 <meta property="og:description" content="Track REP hours with professional IRS compliance tools. Secure time tracking software for real estate professionals seeking REP status qualification."> 87 <meta property="og:type" content="website"> 88 <meta property="og:image" content="https://repsshield.com/repsshield-logo-main.png"> 89 <meta property="og:url" content=""> 90 <meta property="og:locale" content="en_US"> 91 <meta property="og:site_name" content="REPSShield"> 92 93 <!-- Twitter Card Meta Tags --> 94 <meta name="twitter:card" content="summary_large_image"> 95 <meta name="twitter:title" content="IRS REP Tracking & Compliance Tools | REPSShield"> 96 <meta name="twitter:description" content="Professional software to track REP hours and maintain IRS compliance for real estate professionals."> 97 <meta name="twitter:image" content="https://repsshield.com/repsshield-logo-main.png"> 98 99 <!-- Google Workspace Domain Verification --> 100 <meta name="google-site-verification" content="cqP-09PR0BaU2WIhFBeqgZVvytDSELFNgi81gP7DLYc"> 101 102 <!-- Technical Meta Tags --> 103 <meta name="theme-color" content="#55b021"> 104 <meta name="apple-mobile-web-app-capable" content="yes"> 105 <meta name="apple-mobile-web-app-status-bar-style" content="default"> 106 <meta name="apple-mobile-web-app-title" content="REPSShield"> 107 <meta name="mobile-web-app-capable" content="yes"> 108 109 <!-- SEO Technical Tags --> 110 <link rel="canonical" id="canonical-link" href="https://repsshield.com/docs/api"> 111 <meta name="referrer" content="no-referrer-when-downgrade"> 112 <meta http-equiv="content-language" content="en-US"> 113 <meta name="geo.region" content="US"> 114 <meta name="geo.placename" content="United States"> 115 116 <!-- Resource Links --> 117 <link rel="icon" type="image/x-icon" href="/favicon.ico"> 118 <link rel="shortcut icon" href="/favicon.png"> 119 <link rel="apple-touch-icon" href="/repsshield-logo-main.png"> 120 <link rel="manifest" href="/manifest.json"> 121 <link rel="sitemap" type="application/xml" href="/sitemap.xml"> 122 123 <!-- LunchMoney Style System with performance optimization --> 124 <link rel="stylesheet" href="/style.css" media="all"> 125 126 <!-- Performance: Preload critical CSS --> 127 <link rel="stylesheet" href="/style.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> 128 <noscript><link rel="stylesheet" href="/style.css" /></noscript> 129 130 <title>API Documentation â REPSShield</title> 131 132 <!-- Performance Optimization: Resource Hints --> 133 <link rel="preconnect" href="https://www.googletagmanager.com" crossorigin=""> 134 <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin="">
135 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin=""> 136 <link rel="dns-prefetch" href="//www.google-analytics.com"> 137 <link rel="preconnect" href="https://cdn.amplitude.com" crossorigin=""> 138 139 <!-- Deferred Google Tag Manager for better performance --> 140
140<script> 141 // Defer GTM loading until after page load for better performance 142 window.addEventListener("load", function () { 143 (function (w, d, s, l, i) { 144 w[l] = w[l] || []; 145 w[l].push({ 146 "gtm.start": new Date().getTime(), 147 event: "gtm.js", 148 }); 149 var f = d.getElementsByTagName(s)[0], 150 j = d.createElement(s), 151 dl = l != "dataLayer" ? "&l=" + l : ""; 152 j.async = true; 153 j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl; 154 f.parentNode.insertBefore(j, f); 155 })(window, document, "script", "dataLayer", "GTM-TZZ5448V"); 156 }); 157 </script>
157 158 159 <!-- Structured Data for SEO --> 160
160<script type="application/ld+json"> 161 { 162 "@context": "https://schema.org", 163 "@type": "SoftwareApplication", 164 "name": "REPSShield", 165 "applicationCategory": "BusinessApplication", 166 "description": "Professional IRS REP tracking software to track REP hours and maintain IRS compliance for real estate professionals", 167 "url": "https://repsshield.com", 168 "operatingSystem": "Web Browser", 169 "softwareVersion": "1.0", 170 "offers": { 171 "@type": "Offer", 172 "price": "5.00", 173 "priceCurrency": "USD", 174 "priceValidUntil": "2026-12-31" 175 }, 176 "applicationSubCategory": "Time Tracking Software", 177 "featureList": [ 178 "IRS REP tracking", 179 "Track REP hours", 180 "IRS compliance tools", 181 "Material participation tracking", 182 "Audit-ready reports", 183 "Real estate professional time tracking" 184 ], 185 "creator": { 186 "@type": "Organization", 187 "name": "REPSShield", 188 "url": "https://repsshield.com" 189 } 190 } 191 </script>
191 192
vendor: 3,251 bytes, lines 192-268
192<script type="text/javascript"> 193 (function (e, c) { 194 if (!c.__SV) { 195 var l, h; 196 window.mixpanel = c; 197 c._i = []; 198 c.init = function (q, r, f) { 199 function t(d, a) { 200 var g = a.split("."); 201 2 == g.length && ((d = d[g[0]]), (a = g[1])); 202 d[a] = function () { 203 d.push([a].concat(Array.prototype.slice.call(arguments, 0))); 204 }; 205 } 206 var b = c; 207 "undefined" !== typeof f ? (b = c[f] = []) : (f = "mixpanel"); 208 b.people = b.people || []; 209 b.toString = function (d) { 210 var a = "mixpanel"; 211 "mixpanel" !== f && (a += "." + f); 212 d || (a += " (stub)"); 213 return a; 214 }; 215 b.people.toString = function () { 216 return b.toString(1) + ".people (stub)"; 217 }; 218 l = 219 "disable time_event track track_pageview track_links track_forms track_with_groups add_group set_group remove_group register register_once alias unregister identify name_tag set_config reset opt_in_tracking opt_out_tracking has_opted_in_tracking has_opted_out_tracking clear_opt_in_out_tracking start_batch_senders start_session_recording stop_session_recording people.set people.set_once people.unset people.increment people.append people.union people.track_charge people.clear_charges people.delete_user people.remove".split( 220 " " 221 ); 222 for (h = 0; h < l.length; h++) t(b, l[h]); 223 var n = "set set_once union unset remove delete".split(" "); 224 b.get_group = function () { 225 function d(p) { 226 a[p] = function () { 227 b.push([ 228 g, 229 [p].concat(Array.prototype.slice.call(arguments, 0)), 230 ]); 231 }; 232 } 233 for ( 234 var a = {}, 235 g = ["get_group"].concat( 236 Array.prototype.slice.call(arguments, 0) 237 ), 238 m = 0; 239 m < n.length; 240 m++ 241 ) 242 d(n[m]); 243 return a; 244 }; 245 c._i.push([q, r, f]); 246 }; 247 c.__SV = 1.2; 248 var k = e.createElement("script"); 249 k.type = "text/javascript"; 250 k.async = !0; 251 k.src = 252 "undefined" !== typeof MIXPANEL_CUSTOM_LIB_URL 253 ? MIXPANEL_CUSTOM_LIB_URL 254 : "file:" === e.location.protocol && 255 "//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js".match(/^\/\//) 256 ? "https://cdn.mxpnl.com/libs/mixpanel-2-latest.min.js" 257 : "//cdn.mxpnl.com/libs/mixpanel-2-latest.min.js"; 258 e = e.getElementsByTagName("script")[0]; 259 e.parentNode.insertBefore(k, e); 260 } 261 })(document, window.mixpanel || []); 262 mixpanel.init("edfe0ecd8c675dbbc5d0103ee433f5e5", { 263 autocapture: true, 264 // Session recording consolidated into PostHog â Mixpanel keeps 265 // analytics/autocapture only, it no longer records sessions. 266 record_sessions_percent: 0, 267 }); 268 </script>
268 269 270 <!-- Amplitude Analytics (session replay consolidated into PostHog: 271 the Session Replay plugin is no longer loaded or registered) --> 272
272<script defer="" src="https://cdn.amplitude.com/libs/analytics-browser-2.11.1-min.js.gz"></script>
272 273
273<script> 274 window.addEventListener("load", function () { 275 if (window.amplitude?.init) { 276 window.amplitude.init("c705623f4bce3f86b242ede807f4c23e", { 277 autocapture: { elementInteractions: true }, 278 }); 279 } 280 }); 281 </script>
281 282
282<script type="module" crossorigin="" src="/assets/index-DGo-vssZ.js"></script>
282 283 <link rel="modulepreload" crossorigin="" href="/assets/vendor-Qgthw7N1.js"> 284 <link rel="stylesheet" crossorigin="" href="/assets/index-C2doZrJs.css"> 285 <style id="immediate-overlay-kill"> 286 vite-error-overlay, 287 [data-type="error"], 288 .error-overlay { 289 display: none !important; 290 visibility: hidden !important; 291 opacity: 0 !important; 292 pointer-events: none !important; 293 z-index: -999999 !important; 294 } 295 #root { display: block !important; visibility: visible !important; opacity: 1 !important; } 296 body { pointer-events: auto !important; visibility: visible !important; opacity: 1 !important; } 297 </style><link rel="modulepreload" as="script" crossorigin="" href="/assets/ApiDocsPage-CHUb02qa.js"><link rel="modulepreload" as="script" crossorigin="" href="/assets/key-BMkuqIiD.js">
297<script defer="" data-domain="repsshield.com" data-api="https://plausible.io/api/event" src="https://plausible.io/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js"></script>
297</head> 298 <body class="clean-bg"> 299 <!-- Google Tag Manager (noscript) -->
vendor: 80 bytes, lines 299-301
299 300 <noscript><iframe 301 src="https://www.googletagmanager.com/ns.html?id=
301GTM-TZZ5448V
vendor: 126 bytes, lines 301-307
301" 302 height="0" 303 width="0" 304 style="display: none; visibility: hidden" 305 ></iframe 306 ></noscript> 307
307<!-- End Google Tag Manager (noscript) --> 308 309 <!-- Debug: Ensure body and root are visible --> 310 <style> 311 body { 312 visibility: visible !important; 313 opacity: 1 !important; 314 display: block !important; 315 background-color: #f7f7f7; 316 } 317 #root { 318 visibility: visible !important; 319 opacity: 1 !important; 320 display: block !important; 321 min-height: 100vh; 322 } 323 .native-startup-shell { 324 min-height: 100vh; 325 display: flex; 326 align-items: center; 327 justify-content: center; 328 background: #f7f7f7; 329 color: #111827; 330 font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; 331 padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom)); 332 } 333 .native-startup-mascot-wrap { 334 position: relative; 335 display: inline-flex; 336 flex-direction: column; 337 align-items: center; 338 margin: 16px 0 24px; 339 } 340 .native-startup-mascot { 341 width: 96px; 342 height: 96px; 343 -o-object-fit: contain; 344 object-fit: contain; 345 animation: native-startup-bounce 1.4s ease-in-out infinite; 346 } 347 .native-startup-shadow { 348 width: 64px; 349 height: 12px; 350 margin-top: 8px; 351 border-radius: 999px; 352 background: rgba(0, 0, 0, 0.18); 353 filter: blur(2px); 354 animation: native-startup-shadow 1.4s ease-in-out infinite; 355 } 356 @keyframes native-startup-bounce { 357 0%, 100% { transform: translateY(0); } 358 50% { transform: translateY(-14px); } 359 } 360 @keyframes native-startup-shadow { 361 0%, 100% { transform: scale(1); opacity: 0.2; } 362 50% { transform: scale(0.72); opacity: 0.1; } 363 } 364 </style> 365 366 <div id="root" data-prerendered="true"><div class="min-h-screen flex flex-col" data-prerendered="true"><header class="bg-transparent safe-area-top mt-6"><div class="lm-container"><div class="flex justify-between items-center h-16"><a href="/" class="flex items-center hover:opacity-80 transition-opacity"><img src="/repsshield-logo-main.png" alt="REPSShield Logo" class="h-6 w-6 sm:h-8 sm:w-8 mr-2 sm:mr-3"><span class="text-base font-semibold" style="color: var(--color-text); margin: 0px; line-height: 1.5;">REPSShield</span></a><nav class="hidden md:flex items-center space-x-6" role="navigation" aria-label="Main navigation"><ul class="flex items-center space-x-6 list-none m-0 p-0"><li class="relative group"><a aria-haspopup="true" aria-expanded="false" href="/features" class="lm-nav-link flex items-center">Features<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg></a><ul class="absolute left-0 mt-2 w-56 bg-white rounded-md shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50 border list-none p-2" role="menu" aria-label="Features submenu"><li><a role="menuitem" href="/features/time-tracking" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">REP Time Tracking</a></li><li><a role="menuitem" href="/features/material-participation" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">Material Participation Tests</a></li><li><a role="menuitem" href="/features/audit-ready-reporting" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">Audit-Ready Reports</a></li><li><a role="menuitem" href="/features/goal-progress-tracker" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">REP Goal Tracker</a></li><li><a role="menuitem" href="/features/multi-property-support" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">Multi-Property Management</a></li><li><a role="menuitem" href="/features/premium-features" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">AI-Powered Features</a></li></ul></li><li class="relative group"><a aria-haspopup="true" aria-expanded="false" href="/tools" class="lm-nav-link flex items-center">Tools<svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg></a><ul class="absolute left-0 mt-2 w-56 bg-white rounded-md shadow-lg op
366acity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50 border list-none p-2" role="menu" aria-label="Tools submenu"><li><a role="menuitem" href="/tools/rental-calculator" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">Rental Property Calculator</a></li><li><a role="menuitem" href="/tools/brrr-calculator" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">BRRR Strategy Calculator</a></li><li><a role="menuitem" href="/tools/rep-status-calculator" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">REP Status Calculator</a></li><li><a role="menuitem" href="/tools/rep-status-audit-scan" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">REP Audit Scan</a></li><li><a role="menuitem" href="/tools/rep-hours-tracker" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">REP Hours Tracker</a></li><li><a role="menuitem" href="/tools/1031-exchange-timer" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">1031 Exchange Timer</a></li><li><a role="menuitem" href="/tools/cost-seg-estimator" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">Cost Seg Estimator</a></li></ul></li><li><a href="/pricing" class="lm-nav-link">Pricing</a></li><li><a href="/why-reps-shield" class="lm-nav-link">Why REPSShield?</a></li><li><a href="/blog" class="lm-nav-link">Blog</a></li><li><a href="/about" class="lm-nav-link">About</a></li><li><a href="/login"><button class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 touch-manipulation active:scale-[0.97] active:opacity-90 h-12 px-4 py-2 sm:h-10 border border-[#44958C] text-[#44958C] bg-white hover:bg-[#44958C] hover:text-white transition-colors" aria-label="Sign in to your account">Login</button></a></li></ul></nav><div class="md:hidden"><div><button class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium ring-offset-background transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 touch-manipulation active:scale-[0.97] active:opacity-90 hover:bg-accent hover:text-accent-foreground h-10 rounded-md sm:h-9 p-2 touch-target" aria-label="Toggle navigation menu"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-menu h-5 w-5 transition-transform duration-200"><line x1="4" x2="20" y1="12" y2="12"></line><line x1="4" x2="20" y1="6" y2="6"></line><line x1="4" x2="20" y1="18" y2="18"></line></svg></button></div></div></div></div></header><main class="flex-1"><section class="bg-gradient-to-b from-gray-950 to-gray-900 text-white py-16 sm:py-24"><div class="max-w-4xl mx-auto px-4 sm:px-6"><div class="flex items-center gap-2 text-green-400 text-sm font-medium mb-4"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-code-xml h-4 w-4"><path d="m18 16 4-4-4-4"></path><path d="m6 8-4 4 4 4"></path><path d="m14.5 4-5 16"></path></svg>Developer API</div><h1 class="text-3xl text-white sm:text-5xl font-bold mb-4 leading-tight">Log hours from any tool,<br class="hidden sm:block"> automatically.</h1><p class="text-lg text-gray-300 max-w-2xl mb-8">The REPSShield API lets your AI agents, scripts, and automations create time entries on your behalf â so your IRS REPS hours are always up to date.</p><div class="flex flex-wrap gap-3"><a href="/settings"><button class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 touch-manipulation active:scale-[0.97] active:opacity-90 h-12 px-4 py-2 sm:h-10 bg-green-600 hover:bg-green-700 text-white gap-2"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-key h-4 w-4"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"></path><path d="m21 2-9.6 9.6"></path><circle cx="7.5" cy="15.5" r="5.5"></circle></svg>Generate an API key</button></a><a href="#reference"><button class="inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 touch-manipulation active:scale-[0.97] active:opacity-90 border bg-background h-12 px-4 py-2 sm:h-10 border-white/20 text-black hover:bg-white/10 hover:text-white gap-2">View reference<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right h-4 w-4"><path d="m9 18 6-6-6-6"></path></svg></button></a></div></div></section><div class="max-w-4xl mx-auto px-4 sm:px-6 py-12 sm:py-16 space-y-20"><section><h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-8">How it works</h2><div class="grid sm:grid-cols-3 gap-6"><div class="relative p-6 rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><div class="w-9 h-9 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center mb-4 text-green-700 dark:text-green-400 font-bold text-sm">1</div><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-key h-5 w-5 text-gray-400 mb-2"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"></path><path d="m21 2-9.6 9.6"></path><circle cx="7.5" cy="15.5" r="5.5"></circle></svg><h3 class="font-semibold text-gray-900 dark:text-white mb-1">Generate a key</h3><p class="text-sm text-gray-500 dark:text-gray-400">Go to Settings â API Keys and create a named key for each tool you connect.</p></div><div class="relative p-6 rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><div class="w-9 h-9 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center mb-4 text-green-700 dark:text-green-400 font-bold text-sm">2</div><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-terminal h-5 w-5 text-gray-400 mb-2"><polyline points="4 17 10 11 4 5"></polyline><line x1="12" x2="20" y1="19" y2="19"></line></svg><h3 class="font-semibold text-gray-900 dark:text-white mb-1">Discover & log</h3><p class="text-sm text-gray-500 dark:text-gray-400">Fetch your property IDs from GET /api/v1/properties, then POST entries, list them with GET, and refine with PATCH.</p></div><div class="relative p-6 rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><div class="w-9 h-9 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center mb-4 text-green-700 dark:text-green-400 font-bold text-sm">3</div><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock h-5 w-5 text-gray-400 mb-2"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg><h3 class="font-semibold text-gray-900 dark:text-white mb-1">Hours are logged</h3><p class="text-sm text-gray-500 dark:text-gray-400">Entries appear in your Activity Ledger instantly, approved and ready for tax reporting.</p></div></div></section><section><h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">Authentication</h2><p class="text-gray-500 dark:text-gray-400 mb-6">Every request must include your API key using one of these headers:</p><div class="space-y-3"><div class="relative"><pre class="bg-gray-950 text-gray-100 rounded-lg p-4 overflow-x-auto text-sm leading-relaxed font-mono"><code>Authorization: Bearer reps_YOUR_KEY_HERE</code></pre><button class="absolute top-3 right-3 p-1.5 rounded bg-white/10 hover:bg-white/20 transition-colors text-gray-300 hover:text-white" title="Copy"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy h-4 w-4"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></div><p class="text-sm text-gray-500 text-center">or</p><div class="relative"><pre class="bg-gray-950 text-gray-100 rounded-lg p-4 overflow-x-auto text-sm leading-relaxed font-mono"><code>X-API-Key: reps_YOUR_KEY_HERE</code></pre><button class="absolute top-3 right-3 p-1.5 rounded bg-white/10 hover:bg-white/20 transition-colors text-gray-300 hover:text-white" title="Copy"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy h-4 w-4"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></div></div><div class="mt-6 flex items-start gap-3 p-4 rounded-lg bg-amber-50 dark:bg-amber-950 border border-amber-200 dark:border-amber-800 text-sm text-amber-800 dark:text-amber-200"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lock h-4 w-4 mt-0.5 shrink-0"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg><p>Store your API key in an environment variable (e.g. <code class="font-mono bg-amber-100 dar
366k:bg-amber-900 px-1 rounded">REPS_API_KEY</code>). Never hard-code it in source code or commit it to version control.</p></div></section><section><h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">Scopes</h2><p class="text-gray-500 dark:text-gray-400 mb-6">Scopes restrict what an API key is allowed to do. You choose scopes when creating or rotating a key in Settings â API Keys.</p><div class="mb-6 flex items-start gap-3 p-4 rounded-lg bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800 text-sm text-blue-800 dark:text-blue-200"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shield h-4 w-4 mt-0.5 shrink-0 text-blue-600 dark:text-blue-400"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"></path></svg><p><strong>Backward-compatible:</strong> Keys created before scopes were introduced have an empty scopes list (<code class="font-mono bg-blue-100 dark:bg-blue-900 px-1 rounded">scopes: []</code>), which is treated as <strong>unrestricted â full access to all endpoints</strong>. Those keys keep working unchanged.</p></div><div class="overflow-x-auto rounded-xl border border-gray-200 dark:border-gray-800"><table class="w-full text-left"><thead class="bg-gray-50 dark:bg-gray-900"><tr class="border-b border-gray-200 dark:border-gray-700"><th class="px-4 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Scope</th><th class="px-4 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Grants access to</th></tr></thead><tbody class="bg-white dark:bg-gray-950 divide-y divide-gray-100 dark:divide-gray-800"><tr><td class="px-4 py-3"><code class="text-sm font-mono text-green-700 dark:text-green-400">time_entries:read</code></td><td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300"><code class="font-mono text-xs text-gray-500 dark:text-gray-400">GET /api/v1/time-entries</code></td></tr><tr><td class="px-4 py-3"><code class="text-sm font-mono text-green-700 dark:text-green-400">time_entries:write</code></td><td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300"><code class="font-mono text-xs text-gray-500 dark:text-gray-400">POST /api/v1/time-entries, PATCH /api/v1/time-entries/:id</code></td></tr><tr><td class="px-4 py-3"><code class="text-sm font-mono text-green-700 dark:text-green-400">properties:read</code></td><td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300"><code class="font-mono text-xs text-gray-500 dark:text-gray-400">GET /api/v1/properties</code></td></tr></tbody></table></div><p class="mt-4 text-sm text-gray-500 dark:text-gray-400">A key with <strong>no scopes selected</strong> (empty array) is unrestricted and may call any endpoint. Use specific scopes to follow the principle of least privilege.</p></section><section><h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">Error responses</h2><p class="text-gray-500 dark:text-gray-400 mb-6">All API errors return a consistent JSON shape so they are easy to handle programmatically.</p><div class="relative"><pre class="bg-gray-950 text-gray-100 rounded-lg p-4 overflow-x-auto text-sm leading-relaxed font-mono"><code>// 401 â Missing or invalid API key 367{ 368 "error": { 369 "code": "UNAUTHORIZED", 370 "message": "Invalid or expired API key", 371 "suggestion": "Check that your key starts with 'reps_' and has not been revoked." 372 } 373} 374 375// 403 â Correct key, insufficient scope 376{ 377 "error": { 378 "code": "FORBIDDEN", 379 "message": "Missing required scope: time_entries:write", 380 "suggestion": "Rotate your key and add the 'time_entries:write' scope in Settings â API Keys." 381 } 382} 383 384// 400 â Validation failure 385{ 386 "error": { 387 "code": "VALIDATION_ERROR", 388 "message": "Validation failed", 389 "field": "hours", 390 "suggestion": "hours must be between 0.001 and 24" 391 } 392}</code></pre><button class="absolute top-3 right-3 p-1.5 rounded bg-white/10 hover:bg-white/20 transition-colors text-gray-300 hover:text-white" title="Copy"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy h-4 w-4"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></div><div class="mt-4 overflow-x-auto rounded-xl border border-gray-200 dark:border-gray-800"><table class="w-full text-left"><thead class="bg-gray-50 dark:bg-gray-900"><tr class="border-b border-gray-200 dark:border-gray-700"><th class="px-4 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Field</th><th class="px-4 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Always present</th><th class="px-4 py-3 text-xs font-semibold text-gray-500 uppercase tracking-wider">Description</th></tr></thead><tbody class="bg-white dark:bg-gray-950 divide-y divide-gray-100 dark:divide-gray-800"><tr><td class="px-4 py-3"><code class="text-sm font-mono text-green-700 dark:text-green-400">error.code</code></td><td class="px-4 py-3 text-sm text-gray-500">Yes</td><td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">Machine-readable error identifier (e.g. UNAUTHORIZED, FORBIDDEN, VALIDATION_ERROR)</td></tr><tr><td class="px-4 py-3"><code class="text-sm font-mono text-green-700 dark:text-green-400">error.message</code></td><td class="px-4 py-3 text-sm text-gray-500">Yes</td><td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">Human-readable description of what went wrong</td></tr><tr><td class="px-4 py-3"><code class="text-sm font-mono text-green-700 dark:text-green-400">error.field</code></td><td class="px-4 py-3 text-sm text-gray-500">No</td><td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">The request field that caused a validation failure</td></tr><tr><td class="px-4 py-3"><code class="text-sm font-mono text-green-700 dark:text-green-400">error.suggestion</code></td><td class="px-4 py-3 text-sm text-gray-500">No</td><td class="px-4 py-3 text-sm text-gray-600 dark:text-gray-300">Actionable hint to resolve the error</td></tr></tbody></table></div></section><section><h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">Quick start</h2><p class="text-gray-500 dark:text-gray-400 mb-6">Log your first hour in under a minute.</p><div class="flex gap-1 mb-3 border-b border-gray-200 dark:border-gray-800"><button class="px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px border-green-600 text-green-700 dark:text-green-400">
392Curl</button><button class="px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-200">Node</button><button class="px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-200">Python</button><button class="px-4 py-2 text-sm font-medium transition-colors border-b-2 -mb-px border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-200">AI Agent / Tool Schema</button></div><div class="relative"><pre class="bg-gray-950 text-gray-100 rounded-lg p-4 overflow-x-auto text-sm leading-relaxed font-mono"><code>curl -X POST https://repsshield.com/api/v1/time-entries \ 393 -H "Authorization: Bearer reps_YOUR_KEY_HERE" \ 394 -H "Content-Type: application/json" \ 395 -d '{ 396 "date": "2026-03-06", 397 "hours": 1.5, 398 "activityType": "Property Management", 399 "description": "Reviewed lease renewal for Unit 3B" 400 }'</code></pre><button class="absolute top-3 right-3 p-1.5 rounded bg-white/10 hover:bg-white/20 transition-colors text-gray-300 hover:text-white" title="Copy"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy h-4 w-4"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></div></section><section id="reference"><h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">API Reference</h2><p class="text-gray-500 dark:text-gray-400 mb-6">Base URL: <code class="font-mono bg-gray-100 dark:bg-gray-800 px-1.5 py-0.5 rounded text-sm">https://repsshield.com</code></p><div class="flex gap-1 mb-6 border-b border-gray-200 dark:border-gray-800 overflow-x-auto"><button class="flex items-center gap-1.5 px-4 py-2 text-sm font-medium whitespace-nowrap transition-colors border-b-2 -mb-px border-green-600 text-green-700 dark:text-green-400"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock h-3.5 w-3.5"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>POST /time-entries</button><button class="flex items-center gap-1.5 px-4 py-2 text-sm font-medium whitespace-nowrap transition-colors border-b-2 -mb-px border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-200"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-building2 h-3.5 w-3.5"><path d="M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18Z"></path><path d="M6 12H4a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h2"></path><path d="M18 9h2a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2h-2"></path><path d="M10 6h4"></path><path d="M10 10h4"></path><path d="M10 14h4"></path><path d="M10 18h4"></path></svg>GET /properties</button><button class="flex items-center gap-1.5 px-4 py-2 text-sm font-medium whitespace-nowrap transition-colors border-b-2 -mb-px border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-200"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list h-3.5 w-3.5"><path d="M3 12h.01"></path><path d="M3 18h.01"></path><path d="M3 6h.01"></path><path d="M8 12h13"></path><path d="M8 18h13"></path><path d="M8 6h13"></path></svg>GET /time-entries</button><button class="flex items-center gap-1.5 px-4 py-2 text-sm font-medium whitespace-nowrap transition-colors border-b-2 -mb-px border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-200"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pencil h-3.5 w-3.5"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"></path><path d="m15 5 4 4"></path></svg>PATCH /time-entries/:id</button></div><div class="rounded-xl border border-gray-200 dark:border-gray-800 overflow-hidden"><div class="p-6 border-b border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><div class="flex items-center gap-3 mb-4"><span class="font-mono text-xs font-bold px-2 py-0.5 rounded bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">POST</span><code class="font-mono text-sm text-gray-800 dark:text-gray-200">/api/v1/time-entries</code><div class="inline-flex items-center rounded-full border px-2.5 py-0.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground ml-auto text-xs">API key required</div><div class="inline-flex items-center rounded-full border px-2.5 py-0.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent hover:bg-primary/80 bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 text-xs">time_entries:write</div></div><p class="text-sm text-gray-600 dark:text-gray-300 mb-6">Create a new time entry on behalf of the key owner. The primary endpoint for external tools and AI agents logging hours. Optionally attach evidence at creation time via <code class="font-mono text-xs">evidence</code>.</p><h4 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">Request body (JSON)</h4><div class="overflow-x-auto"><table class="w-full text-left"><thead><tr class="border-b border-gray-200 dark:border-gray-700"><th class="pb-2 text-xs font-semibold text-gray-500 dark:text-gray-400 pr-4">Field</th><th class="pb-2 text-xs font-semibold text-gray-500 dark:text-gray-400 pr-4">Type</th><th class="pb-2 text-xs font-semibold text-gray-500 dark:text-gray-400">Description</th></tr></thead><tbody><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">date</code><span class="ml-1.5 text-[10px] font-semibold text-red-500 uppercase tracking-wide">
400req</span></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">string</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">ISO 8601 date â e.g. "2026-03-06"</td></tr><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">hours</code><span class="ml-1.5 text-[10px] font-semibold text-red-500 uppercase tracking-wide">req</span></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">number</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">Decimal hours â min 0.001, max 24. E.g. 1.5 = 1h 30m</td></tr><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">activityType</code><span class="ml-1.5 text-[10px] font-semibold text-red-500 uppercase tracking-wide">req</span></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">string</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">Category of real estate work (see common values below)</td></tr><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">description</code></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">string</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">Free-text description of the specific work done</td></tr><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">propertyId</code></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">number</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">Property ID from GET /api/v1/properties</td></tr><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">propertyType</code></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">string</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">"Long-term Rental" or "Short-term Rental"</td></tr><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">isMaterialParticipation</code></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">boolean</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">Whether this counts toward material participation</td></tr><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">isQualifiedNonMaterial</code></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">boolean</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">Whether this is qualified non-material participation</td></tr><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">timeStarted</code></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">string</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">Exact start timestamp, e.g. "2026-03-06T09:00:00Z"</td></tr><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">timeEnded</code></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">string</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">Exact end timestamp, e.g. "2026-03-06T10:30:00Z"</td></tr><tr class="border-b border-gray-100 dark:border-gray-800 last:border-0"><td class="py-3 pr-4 align-top"><code class="text-sm font-mono text-green-700 dark:text-green-400">evidence</code></td><td class="py-3 pr-4 align-top"><code class="text-xs font-mono text-gray-500 dark:text-gray-400">object | object[]</code></td><td class="py-3 text-sm text-gray-600 dark:text-gray-300">Attach evidence at creation. Single item or array. See evidence schema below.</td></tr></tbody></table></div><div class="mt-6 p-4 rounded-lg bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800"><div class="flex items-center gap-2 mb-2"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-file-up h-4 w-4 text-blue-600 dar
400k:text-blue-400"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"></path><path d="M14 2v4a2 2 0 0 0 2 2h4"></path><path d="M12 12v6"></path><path d="m15 15-3-3-3 3"></path></svg><span class="text-xs font-semibold text-blue-800 dark:text-blue-200 uppercase tracking-wider">Evidence schema</span></div><p class="text-xs text-blue-700 dark:text-blue-300 mb-3">Supply one of two shapes (or an array of either):</p><div class="relative"><pre class="bg-gray-950 text-gray-100 rounded-lg p-4 overflow-x-auto text-sm leading-relaxed font-mono"><code>// Base64-encoded file (recommended for agents) 401{ "base64": "<base64 string>", "filename": "receipt.pdf", "mimeType": "application/pdf" } 402 403// Remote HTTPS URL (must be public; HTTP and private IPs are rejected) 404{ "url": "https://example.com/receipt.pdf", "filename": "receipt.pdf" }</code></pre><button class="absolute top-3 right-3 p-1.5 rounded bg-white/10 hover:bg-white/20 transition-colors text-gray-300 hover:text-white" title="Copy"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy h-4 w-4"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></div><p class="text-xs text-blue-600 dark:text-blue-400 mt-2">Evidence is uploaded to cloud storage and AI-analysed automatically. Supported types: PDF, images (JPEG/PNG/WebP), plain text, Word docs.</p></div><h4 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mt-6 mb-3">Common activityType values</h4><div class="flex flex-wrap gap-2"><code class="text-xs font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-700 dark:text-gray-300">Property Management</code><code class="text-xs font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-700 dark:text-gray-300">Tenant Communication</code><code class="text-xs font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-700 dark:text-gray-300">Maintenance & Repairs</code><code class="text-xs font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-700 dark:text-gray-300">Administrative</code><code class="text-xs font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-700 dark:text-gray-300">Financial Review</code><code class="text-xs font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-700 dark:text-gray-300">Lease Management</code><code class="text-xs font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-700 dark:text-gray-300">Property Marketing</code><code class="text-xs font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-700 dark:text-gray-300">Research & Analysis</code><code class="text-xs font-mono bg-gray-100 dark:bg-gray-800 px-2 py-0.5 rounded text-gray-700 dark:text-gray-300">Travel</code></div></div><div class="p-6 bg-gray-50 dark:bg-gray-950"><h4 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-4">Response</h4><div class="space-y-3"><div class="flex items-center gap-2"><div class="inline-flex items-center rounded-full border px-2.5 py-0.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 border-transparent hover:bg-primary/80 bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 text-xs">201 Created</div><span class="text-sm text-gray-500">Entry created â full time entry object returned</span></div><div class="relative"><pre class="bg-gray-950 text-gray-100 rounded-lg p-4 overflow-x-auto text-sm leading-relaxed font-mono"><code>{ 405 "id": 4821, 406 "userId": "user_abc123", 407 "date": "2026-03-06T00:00:00.000Z", 408 "hours": "1.50", 409 "activityType": "Property Management", 410 "description": "Reviewed lease renewal for Unit 3B", 411 "source": "manual", 412 "approvalStatus": "approved", 413 "createdAt": "2026-03-06T10:32:00.000Z" 414}</code></pre><button class="absolute top-3 right-3 p-1.5 rounded bg-white/10 hover:bg-white/20 transition-colors text-gray-300 hover:text-white" title="Copy"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy h-4 w-4"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></div><div class="grid sm:grid-cols-3 gap-3 pt-2 text-sm"><div class="flex items-start gap-2"><div class="inline-flex items-center rounded-full border px-2.5 py-0.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground text-xs shrink-0">400</div><span class="text-gray-500 dark:text-gray-400">Validation error â check the errors array</span></div><div class="flex items-start gap-2"><div class="inline-flex items-center rounded-full border px-2.5 py-0.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground text-xs shrink-0">401</div><span class="text-gray-500 dark:text-gray-400">Missing, invalid, or expired API key</span></div><div class="flex items-start gap-2"><div class="inline-flex items-center rounded-full border px-2.5 py-0.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground text-xs shrink-0">403</div><span class="text-gray-500 dark:text-gray-400">Key lacks the time_entries:write scope</span></div><div class="flex items-start gap-2"><div class="inline-flex items-center rounded-full border px-2.5 py-0.5 font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 text-foreground text-xs shrink-0">500</div><span class="text-gray-500 dark:text-gray-400">
414Server error â try again or contact support</span></div></div></div></div></div></section><section><h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-2">Key management</h2><p class="text-gray-500 dark:text-gray-400 mb-6">Session-authenticated endpoints (Settings â API Keys) and the agent-onboarding endpoint accessible via an existing API key.</p><div class="space-y-3"><div class="flex items-center gap-3 p-4 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><span class="font-mono text-xs font-bold px-2 py-0.5 rounded bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">GET</span><code class="font-mono text-sm text-gray-700 dark:text-gray-200">/api/api-keys</code><span class="ml-2 text-sm text-gray-500 dark:text-gray-400 hidden sm:block">List your active API keys (hashes are never returned)</span></div><div class="flex items-center gap-3 p-4 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><span class="font-mono text-xs font-bold px-2 py-0.5 rounded bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">POST</span><code class="font-mono text-sm text-gray-700 dark:text-gray-200">/api/api-keys</code><span class="ml-2 text-sm text-gray-500 dark:text-gray-400 hidden sm:block">Create a new key â choose scopes; plaintext shown once and never stored</span></div><div class="flex items-center gap-3 p-4 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><span class="font-mono text-xs font-bold px-2 py-0.5 rounded bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">POST</span><code class="font-mono text-sm text-gray-700 dark:text-gray-200">/api/api-keys/:id/rotate</code><span class="ml-2 text-sm text-gray-500 dark:text-gray-400 hidden sm:block">Atomically revoke a key and reissue a new one with the same name, scopes, and expiry</span></div><div class="flex items-center gap-3 p-4 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><span class="font-mono text-xs font-bold px-2 py-0.5 rounded bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200">DELETE</span><code class="font-mono text-sm text-gray-700 dark:text-gray-200">/api/api-keys/:id</code><span class="ml-2 text-sm text-gray-500 dark:text-gray-400 hidden sm:block">Revoke a key immediately and permanently</span></div><div class="flex items-center gap-3 p-4 rounded-lg border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><span class="font-mono text-xs font-bold px-2 py-0.5 rounded bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">POST</span><code class="font-mono text-sm text-gray-700 dark:text-gray-200">/api/v1/auth/api-keys</code><span class="ml-2 text-sm text-gray-500 dark:text-gray-400 hidden sm:block">Agent self-provisioning â create a new key programmatically using an existing API key</span></div></div><div class="mt-6"><div class="flex items-center gap-2 mb-3"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot h-4 w-4 text-gray-400"><path d="M12 8V4H8"></path><rect width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg><span class="text-sm font-semibold text-gray-700 dark:text-gray-300">Agent self-provisioning example</span></div><div class="relative"><pre class="bg-gray-950 text-gray-100 rounded-lg p-4 overflow-x-auto text-sm leading-relaxed font-mono"><code>// Agents can self-provision keys without a browser session. 415// Requires an active user session cookie (logged-in user delegates to an agent). 416const res = await fetch('https://repsshield.com/api/v1/auth/api-keys', { 417 method: 'POST', 418 headers: { 419 'Authorization': `Bearer ${REPS_API_KEY}`, // existing key with any scope 420 'Content-Type': 'application/json', 421 }, 422 body: JSON.stringify({ 423 name: 'my-agent-v2', 424 scopes: ['time_entries:read', 'time_entries:write'],
425 expiresAt: '2027-01-01T00:00:00Z', // optional 426 }), 427}); 428const { key } = await res.json(); 429// key.plainKey â store this securely; it is shown only once</code></pre><button class="absolute top-3 right-3 p-1.5 rounded bg-white/10 hover:bg-white/20 transition-colors text-gray-300 hover:text-white" title="Copy"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy h-4 w-4"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></div><p class="mt-3 text-sm text-gray-500 dark:text-gray-400">The response body is identical to the regular create-key response: <code class="font-mono bg-gray-100 dark:bg-gray-800 px-1 rounded">{ key: { id, name, scopes, expiresAt, plainKey } }</code>.<code class="font-mono bg-gray-100 dark:bg-gray-800 px-1 rounded ml-1">plainKey</code> is shown only once â store it immediately.</p></div><div class="mt-4 flex items-start gap-3 p-4 rounded-lg bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-800 text-sm text-gray-600 dark:text-gray-300"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-refresh-cw h-4 w-4 mt-0.5 shrink-0 text-gray-400"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"></path><path d="M21 3v5h-5"></path><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"></path><path d="M8 16H3v5"></path></svg><p><strong>Rotating a key</strong> revokes the old key and issues a brand-new one in a single atomic operation. The new key preserves the same name, scopes, and expiry as the original â so your automation only needs to update the secret value.</p></div></section><section><h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6">Security best practices</h2><div class="grid sm:grid-cols-2 gap-4"><div class="flex gap-4 p-5 rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><div class="shrink-0 w-9 h-9 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-lock h-4 w-4 text-green-700 dark:text-green-400"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg></div><div><h3 class="font-semibold text-gray-900 dark:text-white text-sm mb-1">Use environment variables</h3><p class="text-sm text-gray-500 dark:text-gray-400">Never put your API key in source code or commit it to version control. Use a .env file or your tool's secret store.</p></div></div><div class="flex gap-4 p-5 rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><div class="shrink-0 w-9 h-9 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-key h-4 w-4 text-green-700 dark:text-green-400"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4"></path><path d="m21 2-9.6 9.6"></path><circle cx="7.5" cy="15.5" r="5.5"></circle></svg></div><div><h3 class="font-semibold text-gray-900 dark:text-white text-sm mb-1">One key per tool</h3><p class="text-sm text-gray-500 dark:text-gray-400">Name each key after the tool that uses it so you can revoke access for just that tool without affecting others.</p></div></div><div class="flex gap-4 p-5 rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><div class="shrink-0 w-9 h-9 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-zap h-4 w-4 text-green-700 dark:text-green-400"><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"></path></svg></div><div><h3 class="font-semibold text-gray-900 dark:text-white text-sm mb-1">Rotate regularly</h3><p class="text-sm text-gray-500 dark:text-gray-400">Create a new key and revoke the old one from time to time to limit exposure if a key is ever leaked.</p></div></div><div class="flex gap-4 p-5 rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900"><div class="shrink-0 w-9 h-9 rounded-full bg-green-100 dark:bg-green-900 flex items-center justify-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clock h-4 w-4 text-green-700 dark:text-green-400"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg></div><div><h3 class="font-semibold text-gray-900 dark:text-white text-sm mb-1">Set expiration</h3><p class="text-sm text-gray-500 dark:text-gray-400">When creating a key you can supply an expiresAt date so it self-revokes after a certain period automatically.</p></div></div></div></section><section class="rounded-2xl bg-gradient-to-br from-green-600 to-green-800 text-white p-8 sm:p-12 text-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot h-10 w-10 mx-auto mb-4 opacity-80"><path d="M12 8V4H8"></path><rect width="16" height="12" x="4" y="8" rx="2"></rect><path d="M2 14h2"></path><path d="M20 14h2"></path><path d="M15 13v2"></path><path d="M9 13v2"></path></svg><h2 class="text-2xl sm:text-3xl font-bold mb-3">Ready to automate your hours?</h2><p class="text-green-200 mb-6 max-w-md mx-auto">Generate an API key from your account settings and have your first automated entry logged in minutes.</p><a href="/settings"><button class="inline-flex items-center justify-center whitespace-nowrap text-sm r
429ing-offset-background transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 touch-manipulation active:scale-[0.97] active:opacity-90 h-12 rounded-md px-8 sm:h-11 bg-white text-green-700 hover:bg-green-50 gap-2 font-semibold">Go to Settings<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-right h-4 w-4"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg></button></a></section></div></main><footer class="bg-white border-t mt-12 sm:mt-16 spacing-responsive-lg safe-area-bottom"><div class="container-mobile container-safe"><div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-6 gap-8 lg:gap-12"><div class="lg:col-span-1 max-w-md"><div class="flex items-center mb-3 sm:mb-4"><img src="/repsshield-logo-main.png" alt="REPSShield Logo" class="h-6 w-6 sm:h-8 sm:w-8 mr-2 sm:mr-3"><span class="text-responsive-base sm:text-xl font-semibold" style="color: var(--color-text);">REPSShield</span></div><p class="text-gray-600 dark:text-gray-300 mb-3 sm:mb-4 text-responsive-xs">Professional time tracking for Real Estate Professional status qualification. IRS-compliant, audit-ready, AI-powered.</p></div><div><h3 class="text-responsive-sm font-semibold mb-3 sm:mb-4" style="color: var(--color-text);">Features</h3><div class="space-y-1 sm:space-y-2"><a href="/features/time-tracking" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Time Tracking</a><a href="/features/material-participation" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Material Participation</a><a href="/features/audit-ready-reporting" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Audit Reports</a><a href="/features/goal-progress-tracker" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Goal Tracker</a><a href="/features/multi-property-support" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Multi Property</a><a href="/features/premium-features" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">AI Features</a></div></div><div><h3 class="text-responsive-sm font-semibold mb-3 sm:mb-4" style="color: var(--color-text);">Tools</h3><div class="space-y-1 sm:space-y-2"><a href="/tools/rental-calculator" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Rental Calculator</a><a href="/tools/brrr-calculator" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">BRRR Calculator</a><a href="/tools/rep-status-calculator" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">REP Status Calculator</a><a href="/tools/rep-status-audit-scan" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Audit Scan</a><a href="/tools/rep-hours-tracker" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Hours Tracker</a><a href="/tools/1031-exchange-timer" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">1031 Exchange Timer</a></div></div><div><h3 class="text-responsive-sm font-semibold mb-3 sm:mb-4" style="color: var(--color-text);">Evaluate</h3><div class="space-y-1 sm:space-y-2"><a href="/repstracker-alternative" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">REPSTracker Alternative</a></div></div><div><h3 class="text-responsive-sm font-semibold mb-3 sm:mb-4" style="color: var(--color-text);">Company</h3><div class="space-y-1 sm:space-y-2"><a href="/about" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">About</a><a href="/pricing" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Pricing</a><a href="/why-reps-shield" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Why REPSShield?</a><a href="/changelog" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Change Log</a><a href="/docs/api" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Developer API</a><a href="mailto:[email protected]" class="block text-gray-600 hover:text-blue-600 text-responsive-xs touch-target">Email</a></div></div></div>
429<div class="border-t mt-6 sm:mt-8 pt-6 sm:pt-8 text-center text-gray-500 text-responsive-xs"><p class="px-4">© 2025 REPSShield. All rights reserved. | <a href="/privacy" class="hover:text-blue-600 touch-target">Privacy Policy</a> | <a href="/terms" class="hover:text-blue-600 touch-target">Terms of Service</a></p></div></div></footer></div><div role="region" aria-label="Notifications (F8)" tabindex="-1" style="pointer-events: none;"><ol tabindex="-1" class="fixed top-0 z-[11000] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]"></ol></div></div> 430 431 432 433 <!-- Performance Optimized React Pre-Initialization --> 434
434<script type="text/javascript" crossorigin="anonymous" src="https://us-assets.i.posthog.com/static/1.435.3/surveys.js"></script>
434<script type="text/javascript" crossorigin="anonymous" src="https://us-assets.i.posthog.com/array/phc_x5APwUeWZHhqtjerPtcWNqvv2PQN5EsWdYHsqXj9tgwx/config.js"></script>
434<script type="text/javascript" crossorigin="anonymous" src="https://us-assets.i.posthog.com/static/1.435.3/dead-clicks-autocapture.js"></script>
434<script type="text/javascript" crossorigin="anonymous" src="https://us-assets.i.posthog.com/static/1.435.3/posthog-recorder.js"></script>
434<script type="text/javascript" crossorigin="anonymous" src="https://us-assets.i.posthog.com/static/1.435.3/web-vitals-with-attribution.js"></script>
434<script defer=""> 435 // Optimize: Defer execution until DOM is parsed 436 document.addEventListener("DOMContentLoaded", function () { 437 // Environment detection 438 const isDevUrl = !window.parent || window.parent === window; 439 const isPreview = window.parent && window.parent !== window; 440 441 442 443 // Create React namespace with complete hooks implementation IMMEDIATELY 444 window.React = { 445 // Core hooks that the plugin tries to access 446 useState: function (initialState) { 447 448 let state = initialState; 449 const setState = (newState) => { 450 state = 451 typeof newState === "function" ? newState(state) : newState; 452 }; 453 return [state, setState]; 454 }, 455 456 useEffect: function (effect, deps) { 457 458 try { 459 if (typeof effect === "function") { 460 const cleanup = effect(); 461 if (typeof cleanup === "function") { 462 setTimeout(cleanup, 0); 463 } 464 } 465 } catch (e) { 466 console.warn("IMMEDIATE: useEffect error handled safely:", e); 467 } 468 }, 469 470 useRef: function (initialValue) { 471 472 return { current: initialValue }; 473 }, 474 475 useContext: function (context) { 476 477 return context?._currentValue || null; 478 }, 479 480 useReducer: function (reducer, initialState) { 481 482 let state = initialState; 483 const dispatch = (action) => { 484 try { 485 state = reducer(state, action); 486 } catch (e) { 487 console.warn("IMMEDIATE: useReducer error handled:", e); 488 } 489 }; 490 return [state, dispatch]; 491 }, 492 493 useCallback: function (callback, deps) { 494 495 return callback; 496 }, 497 498 useMemo: function (factory, deps) { 499 500 try { 501 return factory(); 502 } catch (e) { 503 console.warn("IMMEDIATE: useMemo error handled:", e); 504 return null; 505 } 506 }, 507 508 // Additional React properties that might be accessed 509 createElement: function (type, props, ...children) { 510 return { 511 type, 512 props: { 513 ...props, 514 children: children.length === 1 ? children[0] : children, 515 }, 516 key: props?.key || null, 517 ref: props?.ref || null, 518 }; 519 }, 520 521 Component: class Component { 522 constructor(props) { 523 this.props = props; 524 this.state = {}; 525 } 526 setState(partialState) { 527 this.state = { ...this.state, ...partialState }; 528 } 529 render() { 530 return null; 531 } 532 }, 533 534 Fragment: "React.Fragment", 535 version: "18.0.0", 536 }; 537 538 // Ensure React hooks are NEVER null by overriding property access 539 const reactProxy = new Proxy(window.React, { 540 get(target, prop) { 541 if (target[prop]) { 542 return target[prop]; 543 } 544 545 // If a hook is accessed that we haven't defined, provide a safe fallback 546 if (prop.startsWith("use")) { 547 548 return function (...args) { 549 console.warn( 550 `IMMEDIATE: Safe fallback ${prop} called with:`, 551 args 552 ); 553 return prop === "useState" 554 ? [null, () => {}] 555 : prop === "useRef" 556 ? { current: null } 557 : null; 558 }; 559 } 560 561 return target[prop]; 562 }, 563 }); 564 565 window.React = reactProxy; 566 567 // Enhanced global error suppression for plugin errors including wallet extensions 568 window.addEventListener( 569 "error", 570 function (event) { 571 const message = event.message || ""; 572 const filename = event.filename || ""; 573 574 // Suppress React hook null access errors 575 if ( 576 message.includes("Cannot read properties of null") && 577 (message.includes("useEffect") || message.includes("useState")) 578 ) { 579 console.log( 580 "IMMEDIATE: Prevented plugin null access error from displaying" 581 ); 582 event.preventDefault(); 583 event.stopPropagation(); 584 return false;
585 } 586 587 // Suppress browser extension property redefinition errors 588 if ( 589 message.includes("Cannot redefine property") && 590 filename.includes("extension") 591 ) { 592 console.log( 593 "IMMEDIATE: Prevented browser extension error from displaying" 594 ); 595 event.preventDefault(); 596 event.stopPropagation(); 597 return false; 598 } 599 600 // Suppress browser extension injection errors 601 if ( 602 message.includes("TypeError") && 603 filename.includes("extension") 604 ) { 605 console.log( 606 "IMMEDIATE: Prevented browser extension error from displaying" 607 ); 608 event.preventDefault(); 609 event.stopPropagation(); 610 return false; 611 } 612 }, 613 true 614 ); // Use capture phase to catch early 615 616 // Enhanced console.error suppression for plugin and wallet extension errors 617 const originalConsoleError = console.error; 618 console.error = function (...args) { 619 const message = args.join(" "); 620 621 // Suppress plugin runtime errors 622 if ( 623 message.includes("[plugin:runtime-error-plugin]") && 624 message.includes("Cannot read properties of null") 625 ) { 626 console.warn("IMMEDIATE: Suppressed plugin error message"); 627 return; 628 } 629 630 // Suppress browser extension property errors 631 if ( 632 message.includes("Cannot redefine property") && 633 filename.includes("extension") 634 ) { 635 console.warn( 636 "IMMEDIATE: Suppressed browser extension error message" 637 ); 638 return; 639 } 640 641 originalConsoleError.apply(console, args); 642 }; 643 644 // Immediate overlay suppression 645 const suppressOverlaysImmediately = () => { 646 const overlays = document.querySelectorAll( 647 'vite-error-overlay, [data-type="error"], .error-overlay' 648 ); 649 overlays.forEach((overlay) => { 650 if ( 651 overlay.textContent && 652 overlay.textContent.includes("runtime-error-plugin") 653 ) { 654 overlay.style.display = "none !important"; 655 overlay.remove(); 656 } 657 }); 658 659 // Add permanent CSS suppression 660 if (!document.getElementById("immediate-overlay-kill")) { 661 const style = document.createElement("style"); 662 style.id = "immediate-overlay-kill"; 663 style.textContent = ` 664 vite-error-overlay, 665 [data-type="error"], 666 .error-overlay { 667 display: none !important; 668 visibility: hidden !important; 669 opacity: 0 !important; 670 pointer-events: none !important; 671 z-index: -999999 !important; 672 } 673 #root { display: block !important; visibility: visible !important; opacity: 1 !important; } 674 body { pointer-events: auto !important; visibility: visible !important; opacity: 1 !important; } 675 `; 676 document.head.appendChild(style); 677 } 678 }; 679 680 // Run immediately and continuously for first few seconds 681 suppressOverlaysImmediately(); 682 const immediateInterval = setInterval(suppressOverlaysImmediately, 25); 683 684 // Early detection: Stop suppression as soon as React content appears 685 const earlyDetection = setInterval(() => { 686 const hasAppElements = 687 window.__APP_INITIALIZED__ || 688 document.querySelector( 689 '.bg-background, .min-h-screen, #root > div, .text-center, .dashboard-container, [class*="card"], [class*="container"]' 690 ) || 691 (document.querySelector("#root") && 692 document.querySelector("#root").innerHTML.length > 100); 693 if (hasAppElements) { 694 console.log( 695 "IMMEDIATE: Early React app detection - stopping overlay suppression" 696 ); 697 clearInterval(immediateInterval);
698 clearInterval(earlyDetection); 699 } 700 }, 200); // Check every 200ms for early detection 701 702 // Stop after React app loads or timeout (Safari gets special treatment) 703 const isSafari = 704 /Safari/.test(navigator.userAgent) && 705 !/Chrome/.test(navigator.userAgent); 706 const timeoutDuration = isSafari ? 60000 : 45000; // Safari gets 60s, others get 45s 707 708 setTimeout(() => { 709 const hasAppElements = 710 window.__APP_INITIALIZED__ || 711 document.querySelector( 712 '.bg-background, .min-h-screen, #root > div, .text-center, .dashboard-container, [class*="card"], [class*="container"]' 713 ) || 714 (document.querySelector("#root") && 715 document.querySelector("#root").innerHTML.length > 100); 716 717 console.log( 718 "IMMEDIATE: Timeout check - App elements found:", 719 hasAppElements 720 ); 721 console.log( 722 "IMMEDIATE: Available elements:", 723 document.querySelector("#root") 724 ? document.querySelector("#root").innerHTML.length + 725 " chars in root" 726 : "No root element" 727 ); 728 729 if (hasAppElements) { 730 console.log( 731 "IMMEDIATE: React app loaded successfully, stopping overlay suppression" 732 ); 733 clearInterval(immediateInterval); 734 clearInterval(earlyDetection); 735 } else { 736 console.log( 737 "IMMEDIATE: React app failed to load after " + 738 timeoutDuration / 1000 + 739 "s - applying fallback strategy" 740 ); 741 clearInterval(immediateInterval); 742 clearInterval(earlyDetection); 743 744 // For mobile devices, especially Safari and post-OAuth, handle specially 745 const isMobile = /Mobi|Android|iPhone|iPad/i.test( 746 navigator.userAgent 747 ); 748 const isAfterOAuth = 749 document.referrer.includes("/api/auth/") || 750 window.location.search.includes("code=") || 751 sessionStorage.getItem("oauth-in-progress") === "true" || 752 sessionStorage.getItem("oauthFlowInitiated") === "true"; 753 754 // Clear OAuth flags 755 sessionStorage.removeItem("oauth-in-progress"); 756 sessionStorage.removeItem("oauthFlowInitiated"); 757 758 // Safari NEVER goes to static page - always reloads or redirects to login 759 if (isSafari) { 760 console.log( 761 "IMMEDIATE: Safari detected - NEVER redirecting to static page" 762 ); 763 console.log( 764 "IMMEDIATE: Safari fallback - redirecting to login page for fresh auth check" 765 ); 766 window.location.href = "/login"; 767 } else if (isMobile || isAfterOAuth) { 768 console.log( 769 "IMMEDIATE: Mobile/post-OAuth detected - reloading page instead of static redirect" 770 ); 771 console.log( 772 "IMMEDIATE: isMobile:", 773 isMobile, 774 "isAfterOAuth:", 775 isAfterOAuth 776 ); 777 window.location.reload(); 778 } else { 779 console.log( 780 "IMMEDIATE: Desktop device - redirecting to login for auth check" 781 ); 782 window.location.href = "/login"; 783 } 784 } 785 }, timeoutDuration); 786 787 788 789 // Fix canonical URL to reflect current page URL 790 // Always uses the production base and strips trailing slashes so 791 // /blog/slug and /blog/slug/ resolve to the same canonical URL. 792 const canonicalLink = document.getElementById("canonical-link"); 793 if (canonicalLink) { 794 let currentPath = location.pathname; 795 if (currentPath.length > 1 && currentPath.endsWith("/")) { 796 currentPath = currentPath.slice(0, -1); 797 } 798 const correctUrl = `https://repsshield.com${currentPath}`; 799 canonicalLink.setAttribute("href", correctUrl); 800 } 801 }); // End DOMContentLoaded 802 </script>
802 803 804 <!-- Performance Monitoring and Optimization --> 805
805<script> 806 // Critical Web Vitals monitoring 807 const observer = new PerformanceObserver((list) => { 808 for (const entry of list.getEntries()) { 809 if (entry.entryType === "measure") { 810 811 } 812 } 813 }); 814 observer.observe({ entryTypes: ["measure"] }); 815 816 // Lazy load non-critical images 817 if ("IntersectionObserver" in window) { 818 const imageObserver = new IntersectionObserver((entries, observer) => { 819 entries.forEach((entry) => { 820 if (entry.isIntersecting) { 821 const img = entry.target; 822 img.src = img.dataset.src; 823 img.classList.remove("lazy"); 824 imageObserver.unobserve(img); 825 } 826 }); 827 }); 828 829 document.addEventListener("DOMContentLoaded", () => { 830 document.querySelectorAll("img[data-src]").forEach((img) => { 831 imageObserver.observe(img); 832 }); 833 }); 834 } 835 </script>
835 836 837 <!-- Optimized Post-OAuth Analytics Loading --> 838
838<script defer=""> 839 // Performance-optimized analytics loading for post-OAuth scenarios 840 window.deferredAnalyticsLoad = function () { 841 842 843 // Google Tag Manager - Load after dashboard render 844 (function (w, d, s, l, i) { 845 w[l] = w[l] || []; 846 w[l].push({ 847 "gtm.start": new Date().getTime(), 848 event: "gtm.js", 849 }); 850 var f = d.getElementsByTagName(s)[0], 851 j = d.createElement(s), 852 dl = l != "dataLayer" ? "&l=" + l : ""; 853 j.async = true; 854 j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl; 855 f.parentNode.insertBefore(j, f); 856 })(window, document, "script", "dataLayer", "GTM-TZZ5448V"); 857 858 // Umami Analytics - Deferred to post-dashboard render using requestIdleCallback 859 function loadUmami() { 860 861 var umamiScript = document.createElement("script"); 862 umamiScript.defer = true; 863 umamiScript.src = "https://cloud.umami.is/script.js"; 864 umamiScript.setAttribute( 865 "data-website-id", 866 "0473b837-662f-4cd9-ada3-b65b89dc1e73" 867 ); 868 umamiScript.onload = function () { 869 870 }; 871 umamiScript.onerror = function () { 872 console.warn("[Analytics] Umami analytics failed to load"); 873 }; 874 document.head.appendChild(umamiScript); 875 } 876 877 // Use requestIdleCallback to load Umami when browser is idle (after dashboard render) 878 if (window.requestIdleCallback) {
879 window.requestIdleCallback(loadUmami, { timeout: 3000 }); 880 } else { 881 setTimeout(loadUmami, 1500); // Fallback with 1.5s delay 882 } 883 }; 884 885 // Global performance optimization for ALL pages 886 window.addEventListener("DOMContentLoaded", function () { 887 // For ALL pages (public and authenticated), defer analytics to improve performance 888 console.log( 889 "[Analytics] Page loaded - deferring analytics for optimal performance" 890 ); 891 892 // Always use maximum delay for public pages performance 893 const isPublicPage = 894 ["/features", "/pricing", "/why-reps-shield", "/about"].includes( 895 window.location.pathname 896 ) || window.location.pathname.startsWith("/features/"); 897 898 if (isPublicPage) { 899 console.log( 900 "[Analytics] Public page detected - maximum deferral for speed" 901 ); 902 // For public marketing pages, defer analytics heavily to maximize page speed 903 if (window.requestIdleCallback) { 904 window.requestIdleCallback(window.deferredAnalyticsLoad, { 905 timeout: 5000, 906 }); 907 } else { 908 setTimeout(window.deferredAnalyticsLoad, 2000); // 2s delay for public pages 909 } 910 } else if ( 911 window.location.pathname === "/" || 912 window.location.hash === "#/" 913 ) { 914 console.log( 915 "[Analytics] Dashboard detected - deferring analytics until after render" 916 ); 917 // Wait for dashboard to be fully rendered before loading analytics 918 function waitForDashboard() { 919 if ( 920 document.querySelector( 921 '[data-testid="dashboard-content"], .dashboard-container, .min-h-screen' 922 ) 923 ) { 924 925 window.deferredAnalyticsLoad(); 926 } else { 927 setTimeout(waitForDashboard, 100); 928 } 929 } 930 setTimeout(waitForDashboard, 500); // Give dashboard time to start rendering 931 } else { 932 // For other pages, load analytics with moderate delay 933 if (window.requestIdleCallback) { 934 window.requestIdleCallback(window.deferredAnalyticsLoad, { 935 timeout: 3000, 936 }); 937 } else { 938 setTimeout(window.deferredAnalyticsLoad, 1000); 939 } 940 } 941 }); 942 </script>
942 943 <!-- This is a replit script which adds a banner on the top of the page when opened in development mode outside the replit environment --> 944 <!-- Commented out to prevent blue debug banner showing in production 945
945<script type="text/javascript" src="https://replit.com/public/js/replit-dev-banner.js"></script>
945 946 --> 947 948 <!-- React App Entry Point - MUST be last to ensure all scripts are loaded first --> 949 950 951
952<script type="text/javascript" id="" charset="">var script=document.createElement("script");script.defer=!0;script.dataset.domain="repsshield.com";script.dataset.api="https://plausible.io/api/event";script.src="https://plausible.io/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js";document.getElementsByTagName("head")[0].appendChild(script);</script>
952 953
954<script type="text/javascript" id="" charset="">window.plausible=window.plausible||function(){(window.plausible.q=window.plausible.q||[]).push(arguments)};</script>
954 955</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.