PageSourceSearch

https://repsshield.com/docs/api/

html repsshield.com collected 2026-10-02 01:59:55 UTC 89,346 bytes, 955 lines download raw bytes

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&amp;cx=c&amp;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 &amp; 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 &amp; 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 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_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 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_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 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_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 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_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 &amp; 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": "&lt;base64 string&gt;", "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 &amp; 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 &amp; 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 [&amp;_svg]:pointer-events-none [&amp;_svg]:size-4 [&amp;_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.