This commit is contained in:
Bryce
2025-10-22 17:34:13 -07:00
commit 2383d1ad9a
15 changed files with 1372 additions and 0 deletions

21
.env Normal file
View File

@@ -0,0 +1,21 @@
# Flask
FLASK_SECRET_KEY=replace-with-long-random-string
# Firebase Admin (choose ONE of these approaches)
# 1) Path to JSON creds file
GOOGLE_APPLICATION_CREDENTIALS=./rothbard-service-account.json
# 2) Or inline JSON (escaped as single line)
# FIREBASE_SERVICE_ACCOUNT_JSON={"type":"service_account",...}
# Filevine auth
FILEVINE_CLIENT_ID=4F18738C-107A-4B82-BFAC-308F1B6A626A
FILEVINE_CLIENT_SECRET=*2{aXWvYN(9!BiYUXC_tXj^n8
FILEVINE_PERSONAL_ACCESS_TOKEN=B87C144C35A570077F9D7D70CDC2280DD023142D1BFCE2C45B6CE8934600EA6F
FILEVINE_ORG_ID=9227
FILEVINE_USER_ID=100510
# Front-end Firebase (public — safe to expose)
FIREBASE_API_KEY=AIzaSyC7t2D0uSuc1hm6ZEkfUMVPtkaE2TXF1a0
FIREBASE_AUTH_DOMAIN=rothbard-3f496.firebaseapp.com
FIREBASE_PROJECT_ID=rothbard-3f496
FIREBASE_APP_ID=1:90016977941:web:da38d57849021115e52a1c

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.venv/**
.lsp/**
.clj-kondo/**

19
README.md Normal file
View File

@@ -0,0 +1,19 @@
Rothbard Client Portal
This is a server-side rendered client portal for rothbard law group. Users can log in, and once they've been configured, can see a list of cases from the filevine API.
Todo list
* Lock down firebase
* Set firebase up with terraform
* Allow users to store their preferences on which columns they see
* Styling
* New user sign up
* Set up users
Project Structure:
* static/ - static assets, html, javascript etc
* templates/ jinja templates
* app.py - core login
* examples/ sample filevine api requests/responses for digging into
* generate_sample.py - generates the examples

270
app.py Normal file
View File

@@ -0,0 +1,270 @@
import json
import os
from functools import wraps
from datetime import datetime, timedelta
from flask import Flask, render_template, request, redirect, url_for, session, abort, jsonify
from dotenv import load_dotenv
import firebase_admin
from firebase_admin import credentials, auth as fb_auth, firestore
import requests
load_dotenv()
app = Flask(__name__)
app.secret_key = os.environ.get("FLASK_SECRET_KEY", os.urandom(32))
# --- Firebase Admin init ---
_creds = None
json_inline = os.environ.get("FIREBASE_SERVICE_ACCOUNT_JSON")
file_path = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS")
if json_inline:
_creds = credentials.Certificate(json.loads(json_inline))
elif file_path and os.path.exists(file_path):
_creds = credentials.Certificate(file_path)
else:
raise RuntimeError("Firebase credentials not configured. Set GOOGLE_APPLICATION_CREDENTIALS or FIREBASE_SERVICE_ACCOUNT_JSON.")
firebase_admin.initialize_app(_creds)
db = firestore.client()
# --- Filevine env ---
FV_CLIENT_ID = os.environ.get("FILEVINE_CLIENT_ID")
FV_CLIENT_SECRET = os.environ.get("FILEVINE_CLIENT_SECRET")
FV_PAT = os.environ.get("FILEVINE_PERSONAL_ACCESS_TOKEN")
FV_ORG_ID = os.environ.get("FILEVINE_ORG_ID")
FV_USER_ID = os.environ.get("FILEVINE_USER_ID")
if not all([FV_CLIENT_ID, FV_CLIENT_SECRET, FV_PAT, FV_ORG_ID, FV_USER_ID]):
print("[WARN] Missing one or more Filevine env vars — dashboard will fail until set.")
# --- Helpers ---
def login_required(view):
@wraps(view)
def wrapped(*args, **kwargs):
uid = session.get("uid")
if not uid:
return redirect(url_for("login"))
return view(*args, **kwargs)
return wrapped
def get_user_profile(uid: str):
"""Fetch user's Firestore profile: users/{uid} => { enabled, caseEmail }"""
doc_ref = db.collection("users").document(uid)
snap = doc_ref.get()
if not snap.exists:
# bootstrap a placeholder doc so admins can fill it in
doc_ref.set({"enabled": False}, merge=True)
return {"enabled": False, "caseEmail": None}
data = snap.to_dict() or {}
return {"enabled": bool(data.get("enabled", False)), "caseEmail": data.get("caseEmail")}
# --- Routes ---
@app.route("/")
def index():
uid = session.get("uid")
if not uid:
return redirect(url_for("login"))
profile = get_user_profile(uid)
if profile.get("enabled") and profile.get("caseEmail"):
return redirect(url_for("dashboard"))
return redirect(url_for("welcome"))
@app.route("/login")
def login():
# Pass public Firebase config to template
fb_public = {
"apiKey": os.environ.get("FIREBASE_API_KEY", ""),
"authDomain": os.environ.get("FIREBASE_AUTH_DOMAIN", ""),
"projectId": os.environ.get("FIREBASE_PROJECT_ID", ""),
"appId": os.environ.get("FIREBASE_APP_ID", ""),
}
return render_template("login.html", firebase_config=fb_public)
@app.route("/session_login", methods=["POST"])
def session_login():
"""Exchanges Firebase ID token for a server session."""
id_token = request.json.get("idToken") if request.is_json else request.form.get("idToken")
if not id_token:
abort(400, "Missing idToken")
try:
decoded = fb_auth.verify_id_token(id_token)
uid = decoded["uid"]
session.clear()
session["uid"] = uid
# Optional: short session
session["expires_at"] = (datetime.utcnow() + timedelta(hours=8)).isoformat()
return jsonify({"ok": True})
except Exception as e:
print("[ERR] session_login:", e)
abort(401, "Invalid ID token")
@app.route("/logout")
def logout():
session.clear()
return redirect(url_for("login"))
@app.route("/welcome")
@login_required
def welcome():
uid = session.get("uid")
profile = get_user_profile(uid)
return render_template("welcome.html", profile=profile)
# --- Filevine API ---
def get_filevine_bearer():
url = "https://identity.filevine.com/connect/token"
data = {
"client_id": FV_CLIENT_ID,
"client_secret": FV_CLIENT_SECRET,
"grant_type": "personal_access_token",
"scope": "fv.api.gateway.access tenant filevine.v2.api.* email openid fv.auth.tenant.read",
"token": FV_PAT,
}
headers = {"Accept": "application/json"}
resp = requests.post(url, data=data, headers=headers, timeout=30)
resp.raise_for_status()
js = resp.json()
print(js)
return js.get("access_token")
def list_all_projects(bearer: str):
base = "https://api.filevineapp.com/fv-app/v2/Projects"
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {bearer}",
"x-fv-orgid": str(FV_ORG_ID),
"x-fv-userid": str(FV_USER_ID),
}
print(headers)
results = []
last_id = None
tries = 0
while True:
tries += 1
url = base
params = {}
if last_id is not None:
# Some deployments use LastID/Offset pagination; adapt if needed
params["lastID"] = last_id
r = requests.get(url, headers=headers, params=params, timeout=30)
print(r.content)
r.raise_for_status()
page = r.json()
items = page.get("items", [])
results.extend(items)
has_more = page.get("hasMore")
last_id = page.get("lastID")
if not has_more:
break
# Safety valve
if tries > 200:
break
print("RESULTS", results)
return results
def fetch_project_detail(bearer: str, project_id_native: int):
url = f"https://api.filevineapp.com/fv-app/v2/Projects/{project_id_native}"
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {bearer}",
"x-fv-orgid": str(FV_ORG_ID),
"x-fv-userid": str(FV_USER_ID),
}
r = requests.get(url, headers=headers, timeout=30)
r.raise_for_status()
return r.json()
def fetch_client(bearer: str, client_id_native: int):
url = f"https://api.filevineapp.com/fv-app/v2/contacts/{client_id_native}"
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {bearer}",
"x-fv-orgid": str(FV_ORG_ID),
"x-fv-userid": str(FV_USER_ID),
}
r = requests.get(url, headers=headers, timeout=30)
r.raise_for_status()
return r.json()
def fetch_contacts(bearer: str, project_id_native: int):
url = f"https://api.filevineapp.com/fv-app/v2/projects/{project_id_native}/contacts"
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {bearer}",
"x-fv-orgid": str(FV_ORG_ID),
"x-fv-userid": str(FV_USER_ID),
}
r = requests.get(url, headers=headers, timeout=30)
r.raise_for_status()
return r.json().get("items")
@app.route("/dashboard")
@login_required
def dashboard():
uid = session.get("uid")
profile = get_user_profile(uid)
if not profile.get("enabled"):
return redirect(url_for("welcome"))
case_email = profile.get("caseEmail")
if not case_email:
return redirect(url_for("welcome"))
# 1) Bearer token
bearer = get_filevine_bearer()
# 2) List projects (all pages)
projects = list_all_projects(bearer)
# 3) Filter to ProjectEmailAddress == caseEmail
#filtered = [p for p in projects if str(p.get("ProjectEmailAddress", "")).lower() == str(case_email).lower()]
filtered = projects
# 4) Fetch details for each
detailed_rows = []
for p in filtered:
pid = (p.get("projectId") or {}).get("native")
c = fetch_client(bearer, (p.get("clientId") or {}).get("native"))
cs = fetch_contacts(bearer, pid)
print("CS IS", cs)
if pid is None:
continue
try:
detail = fetch_project_detail(bearer, pid)
except Exception as e:
print(f"[WARN] detail fetch failed for {pid}: {e}")
detail = {}
row = {
"client": c.get("firstName"),
"matter_description": p.get("projectName"),
"contacts": cs,
"ProjectEmailAddress": p.get("projectEmailAddress"),
"Number": p.get("number"),
"IncidentDate": (p.get("incidentDate") or detail.get("incidentDate")),
"ProjectId": pid,
"ProjectName": p.get("projectName") or detail.get("projectName"),
"ProjectUrl": p.get("projectUrl") or detail.get("projectUrl"),
}
detailed_rows.append(row)
# 5) Render table
return render_template("dashboard.html", rows=detailed_rows, case_email=case_email)
if __name__ == "__main__":
app.run(debug=True, host="0.0.0.0", port=5000)

85
examples/client.json Normal file
View File

@@ -0,0 +1,85 @@
{
"sample_request": {
"url": "https://api.filevineapp.com/fv-app/v2/contacts/43125866",
"headers": {
"Accept": "application/json",
"Authorization": "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6Ijg2NjRFMkY0MDNCQjIxMzk2MzQ4NUFDOEI0MzVGMEJBOTgxNTBFN0RSUzUxMiIsInR5cCI6ImF0K2p3dCIsIng1dCI6ImhtVGk5QU83SVRsalNGckl0RFh3dXBnVkRuMCJ9.eyJuYmYiOjE3NjExNzg4NzUsImV4cCI6MTc2MTE4MDY3NSwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS5maWxldmluZS5jb20iLCJhdWQiOlsiZmlsZXZpbmUudjIuYXBpIiwiZnYuYXBpLmdhdGV3YXkiLCJmdi5hdXRoIl0sImNsaWVudF9pZCI6IjRGMTg3MzhDLTEwN0EtNEI4Mi1CRkFDLTMwOEYxQjZBNjI2QSIsInN1YiI6ImY3MDQ4NGZmLTQ5MjItNDliMy05MWFkLTE2YjA5Mjk5MGIzMCIsImF1dGhfdGltZSI6MTc2MTE3ODg3NSwiaWRwIjoibG9jYWwiLCJwYXRfaWQiOiJmUkEwbHRoSnp5aTBBWU9ZS1F4WGVlckczUHc1MXEyMmh4cTlzbk4zL2V3PSIsInBhdF9uYW1lIjoiQnJ5Y2UgQ292ZXJ0IiwicGF0X3ZlcnNpb24iOiIxIiwidGVuYW50X2ZybiI6ImZybjpmaWxldmluZTp1cy1wcm9kOmZpbGV2aW5lLWFwcDo6OnRlbmFudFxcMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwidGVuYW50X2lkIjoiMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwianRpIjoiMkNENzYzNUFGNkU5RjQ0RjY2NzI1RTkyREY3RUI2NDciLCJpYXQiOjE3NjExNzg4NzUsInNjb3BlIjpbImVtYWlsIiwiZmlsZXZpbmUudjIuYXBpLioiLCJmdi5hcGkuZ2F0ZXdheS5hY2Nlc3MiLCJmdi5hdXRoLnRlbmFudC5yZWFkIiwib3BlbmlkIiwidGVuYW50Il0sImFtciI6WyJwZXJzb25hbF9hY2Nlc3NfdG9rZW4iXX0.E-TzpOEg6TO0ab65oq_rIambdugvK435q-yR1miardzwt5mP1Bz4aiLCurnx3FmkxhMSTnIOC8jzX73mG_lZL22FFctsB5EWt7WrUScqVAmTtG4hfPQOp4BM007nbXiKWwEXejBhyV8bsxkRkjv1gj6OlaCnvLkiDCgiD93RWpo6LSMi7nry1bNUXI2eN-0yXXrvDEiwvfY2meO1lvVwEUktLPy45_ehZ6IOc6ZIDw3zMLkqZjj046JRCsT9L8w-oNHq5xerRC4RcssIklsn9KH7M_oDrDtW20gLcTJ0mubk_HcTc1oQh-loe89UHFg-tiB63LHng9Vj3JnyZPvEjg",
"x-fv-orgid": "9227",
"x-fv-userid": "100510"
},
"params": {},
"method": "GET"
},
"sample_response": {
"status_code": 200,
"json": {
"personId": {
"native": 43125866,
"partner": null
},
"firstName": "Twin Pines Apartments (Twin Pine LLC)",
"middleName": "",
"lastName": "",
"isSingleName": true,
"fullName": "Twin Pines Apartments (Twin Pine LLC)",
"pictureUrl": "/images/Default6eba3d0d-6227-44c3-b907-8311c3d4da82.png",
"pictureKey": "Default6eba3d0d-6227-44c3-b907-8311c3d4da82.png",
"fromCompany": "",
"personTypes": [
"Client/ Property"
],
"uniqueId": "65333863-f119-4e10-9473-992b99fbac16",
"searchNames": [
"twin",
"pines",
"apartments",
"(twin",
"pine",
"llc)",
"twin pines apartments (twin pine llc)"
],
"phones": [],
"emails": [],
"addresses": [],
"prefix": "",
"hashtags": [],
"suffix": "",
"jobTitle": "",
"department": "",
"modifiedDate": "2025-10-22T15:06:31.993Z",
"links": {
"self": "/contacts/43125866",
"projects": "/contacts/43125866/projects",
"phones": "/contacts/43125866/phones",
"emailAddresses": "/contacts/43125866/emailAddresses",
"addresses": "/contacts/43125866/addresses"
}
},
"headers": {
"Date": "Thu, 23 Oct 2025 00:21:16 GMT",
"Content-Type": "application/json; charset=utf-8",
"Transfer-Encoding": "chunked",
"Connection": "keep-alive",
"Server": "cloudflare",
"access-control-allow-headers": "Content-Type, x-fv-orgid, x-fv-clientip, x-fv-userid, authorization, x-fv-application",
"access-control-allow-methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS, LOCK, UNLOCK, PROPPATCH, PROPFIND",
"access-control-allow-origin": "*",
"Cache-Control": "no-store, must-revalidate, no-cache, max-age=0, private",
"ratelimit-limit": "10;r=3300;w=60;c=Customer Temp",
"ratelimit-remaining": "9;r=3300;w=60;c=Customer Temp",
"content-security-policy": "default-src 'self';child-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://app.vinesign.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com https://app.pendo.io https://feedback.us.pendo.io docs.google.com https://feedback.filevine.com *.newrelic.com *.filev.io *.flvn.io filev.io flvn.io 'self';connect-src *.filevinedev.com *.filevineapp.com *.filevine.ca *.filevine.com *.filevinegov.com *.fvauth.com https://app.vinesign.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com *.nr-data.net *.pendo.io *.pdftron.com *.typeform.com *.newrelic.com https://app.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://localhost:8080 *.filev.io *.flvn.io filev.io flvn.io 'self' blob: wss:;font-src *.bootstrapcdn.com fonts.gstatic.com *.typekit.net *.typeform.com 'self' data: blob:;frame-src *;frame-ancestors https://*.filevineapp.com https://app.pendo.io 'self';img-src *.typekit.net *.typeform.com https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-globalproducts-prod-us-logos.s3.us-west-2.amazonaws.com https://us.fv-globalproducts-logos.prod.filevine.com https://fv-prod-us-shard-h-fv-internal-image.s3.amazonaws.com https://fv-prod-us-shard-h-fv-internal-image.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io *.kaywa.com www.googletagmanager.com 'self' data: blob: cid:;manifest-src 'self';media-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com https://us-shard-h-discussions.filevineapp.com *.filev.io *.flvn.io filev.io flvn.io 'self';object-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io 'self';script-src *.bootstrapcdn.com *.typekit.net *.typeform.com *.newrelic.com *.nr-data.net https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://code.jquery.com https://localhost:8080 https://www.googletagmanager.com 'unsafe-inline' 'unsafe-eval' 'self' blob:;style-src *.bootstrapcdn.com fonts.googleapis.com *.typekit.net *.typeform.com https://app.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://cdn.pendo.io https://data.pendo.io 'unsafe-inline' 'self';worker-src 'self' blob: 'unsafe-inline'",
"x-filevine-api-version": "3.3426.3.0",
"x-fv-correlation-id": "0a3acd7278c6486796d853ddf232e48f",
"x-aspnet-version": "4.0.30319",
"x-powered-by": "ASP.NET",
"x-content-type-options": "nosniff",
"x-frame-option": "SAMEORIGIN",
"x-xss-protection": "1; mode=block",
"x-fv-gateway-correlation-id": "0a3acd7278c6486796d853ddf232e48f",
"cf-cache-status": "DYNAMIC",
"Content-Encoding": "gzip",
"CF-RAY": "992d12c6c95790db-SEA",
"alt-svc": "h3=\":443\"; ma=86400"
}
}
}

View File

@@ -0,0 +1,246 @@
{
"sample_request": {
"url": "https://api.filevineapp.com/fv-app/v2/Projects/15905506/contacts",
"headers": {
"Accept": "application/json",
"Authorization": "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6Ijg2NjRFMkY0MDNCQjIxMzk2MzQ4NUFDOEI0MzVGMEJBOTgxNTBFN0RSUzUxMiIsInR5cCI6ImF0K2p3dCIsIng1dCI6ImhtVGk5QU83SVRsalNGckl0RFh3dXBnVkRuMCJ9.eyJuYmYiOjE3NjExNzg4NzUsImV4cCI6MTc2MTE4MDY3NSwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS5maWxldmluZS5jb20iLCJhdWQiOlsiZmlsZXZpbmUudjIuYXBpIiwiZnYuYXBpLmdhdGV3YXkiLCJmdi5hdXRoIl0sImNsaWVudF9pZCI6IjRGMTg3MzhDLTEwN0EtNEI4Mi1CRkFDLTMwOEYxQjZBNjI2QSIsInN1YiI6ImY3MDQ4NGZmLTQ5MjItNDliMy05MWFkLTE2YjA5Mjk5MGIzMCIsImF1dGhfdGltZSI6MTc2MTE3ODg3NSwiaWRwIjoibG9jYWwiLCJwYXRfaWQiOiJmUkEwbHRoSnp5aTBBWU9ZS1F4WGVlckczUHc1MXEyMmh4cTlzbk4zL2V3PSIsInBhdF9uYW1lIjoiQnJ5Y2UgQ292ZXJ0IiwicGF0X3ZlcnNpb24iOiIxIiwidGVuYW50X2ZybiI6ImZybjpmaWxldmluZTp1cy1wcm9kOmZpbGV2aW5lLWFwcDo6OnRlbmFudFxcMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwidGVuYW50X2lkIjoiMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwianRpIjoiRTg1ODdBNDY3NkI1OTg2M0U0OTAxNkNFRTYxNDNFNjciLCJpYXQiOjE3NjExNzg4NzUsInNjb3BlIjpbImVtYWlsIiwiZmlsZXZpbmUudjIuYXBpLioiLCJmdi5hcGkuZ2F0ZXdheS5hY2Nlc3MiLCJmdi5hdXRoLnRlbmFudC5yZWFkIiwib3BlbmlkIiwidGVuYW50Il0sImFtciI6WyJwZXJzb25hbF9hY2Nlc3NfdG9rZW4iXX0.mjeEZUAE466DLME0jmRu_znERUH6_85AitHzgDrQlWPpeIy_18-B5x9hG2-wAp2s_WGdjn2eRc8qmy5wD9vbw4k1-L3HNhfI_9s24MGbanXWeu9eGHenY092D12W4eoJYlYCCpbGhqPNbql021hbU6TZTTGs2IMfwBy9k5H2HVCkPd2NbswuEsTt_M1XUUe-6duqEIGWdYQ097xGcVl3tGYgAxR6nyICyiovxVRZXsuKGK0a5r_7KKSZwM6v1brB9V08oBBsGoTRaLe_uxG-YHrMSPZ0JXvLxvzT8XXLh5RMXd2ijekHQ9nMKehmxojiQKg20r8_4kUtzZlkyeL_WQ",
"x-fv-orgid": "9227",
"x-fv-userid": "100510"
},
"params": {},
"method": "GET"
},
"sample_response": {
"status_code": 200,
"json": {
"count": 3,
"offset": 0,
"limit": 50,
"hasMore": false,
"requestedFields": "*",
"items": [
{
"projectId": {
"native": 15905506,
"partner": null
},
"orgContactId": {
"native": 43125848,
"partner": null
},
"roles": [
"Property Contacts: Property Manager 1"
],
"orgContact": {
"personId": {
"native": 43125848,
"partner": null
},
"firstName": "Brian",
"middleName": "Robert",
"lastName": "Testy",
"isSingleName": false,
"fullName": "Brian Robert Testy",
"primaryEmail": "brianskarbek@gmail.com",
"pictureUrl": "/images/Default64896de9-7ee8-47a4-bcd2-1df186e6230a.png",
"pictureKey": "Default64896de9-7ee8-47a4-bcd2-1df186e6230a.png",
"personTypes": [
"Property Manager"
],
"uniqueId": "4daf252f-db81-4ca7-b7e4-0a5fd801170c",
"searchNames": [
"brian",
"robert",
"testy",
"brian robert testy",
"brian testy"
],
"phones": [
{
"phoneId": {
"native": 74619813,
"partner": null
},
"number": "4084821733",
"rawNumber": "4084821733",
"isSmsable": false,
"isFaxable": false,
"links": {}
}
],
"emails": [
{
"emailId": {
"native": 19265473,
"partner": null
},
"address": "brianskarbek@gmail.com",
"links": {}
}
],
"addresses": [
{
"addressId": {
"native": 56313190,
"partner": null
},
"line1": "2926 Neal Ave",
"line2": "",
"city": "San Jose",
"state": "CA",
"postalCode": "95128",
"fullAddress": "2926 Neal Ave, San Jose, CA 95128",
"links": {}
}
],
"hashtags": [],
"modifiedDate": "2025-10-18T14:24:54.16Z",
"links": {
"self": "/contacts/43125848",
"projects": "/contacts/43125848/projects",
"phones": "/contacts/43125848/phones",
"emailAddresses": "/contacts/43125848/emailAddresses",
"addresses": "/contacts/43125848/addresses"
}
},
"links": {}
},
{
"projectId": {
"native": 15905506,
"partner": null
},
"orgContactId": {
"native": 43193362,
"partner": null
},
"roles": [
"Defendants: Defendant Contact"
],
"orgContact": {
"personId": {
"native": 43193362,
"partner": null
},
"firstName": "Bad",
"lastName": "Guy",
"isSingleName": false,
"fullName": "Bad Guy",
"pictureUrl": "/images/Defaultf6812c31-f9e7-4961-b7da-89adcd48dbac.png",
"pictureKey": "Defaultf6812c31-f9e7-4961-b7da-89adcd48dbac.png",
"personTypes": [
"Defendant"
],
"uniqueId": "3368bc70-4aed-4349-9fd6-0cd2dca654ae",
"searchNames": [
"bad",
"guy",
"bad guy"
],
"phones": [],
"emails": [],
"addresses": [],
"hashtags": [],
"modifiedDate": "2025-10-22T15:29:38.463Z",
"links": {
"self": "/contacts/43193362",
"projects": "/contacts/43193362/projects",
"phones": "/contacts/43193362/phones",
"emailAddresses": "/contacts/43193362/emailAddresses",
"addresses": "/contacts/43193362/addresses"
}
},
"links": {}
},
{
"projectId": {
"native": 15905506,
"partner": null
},
"orgContactId": {
"native": 43182533,
"partner": null
},
"roles": [
"Matter Overview: Signing Attorney"
],
"orgContact": {
"personId": {
"native": 43182533,
"partner": null
},
"firstName": "Brian",
"lastName": "Skarbek",
"isSingleName": false,
"fullName": "Brian Skarbek",
"primaryEmail": "Brian@ToddRothbardLaw.com",
"pictureUrl": "/images/Default96eecdd8-0702-4099-9ef2-254bb3a788fa.png",
"pictureKey": "Default96eecdd8-0702-4099-9ef2-254bb3a788fa.png",
"personTypes": [
"Attorney"
],
"uniqueId": "f0cab457-e837-4341-843d-a8afb4265beb",
"searchNames": [
"brian",
"skarbek",
"brian skarbek"
],
"phones": [],
"emails": [
{
"emailId": {
"native": 19294763,
"partner": null
},
"address": "Brian@ToddRothbardLaw.com",
"links": {}
}
],
"addresses": [],
"hashtags": [],
"modifiedDate": "2025-10-20T23:47:10.213Z",
"links": {
"self": "/contacts/43182533",
"projects": "/contacts/43182533/projects",
"phones": "/contacts/43182533/phones",
"emailAddresses": "/contacts/43182533/emailAddresses",
"addresses": "/contacts/43182533/addresses"
}
},
"links": {}
}
],
"links": {
"self": "/contacts?firstName=&lastName=&fullName=&nickName=&personType=&phone=&email=&searchTerm=&offset=0&limit=50&requestedFields=*",
"prev": null,
"next": null
}
},
"headers": {
"Date": "Thu, 23 Oct 2025 00:21:15 GMT",
"Content-Type": "application/json; charset=utf-8",
"Transfer-Encoding": "chunked",
"Connection": "keep-alive",
"Server": "cloudflare",
"access-control-allow-headers": "Content-Type, x-fv-orgid, x-fv-clientip, x-fv-userid, authorization, x-fv-application",
"access-control-allow-methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS, LOCK, UNLOCK, PROPPATCH, PROPFIND",
"access-control-allow-origin": "*",
"Cache-Control": "no-store, must-revalidate, no-cache, max-age=0, private",
"ratelimit-limit": "10;r=3300;w=60;c=Customer Temp",
"ratelimit-remaining": "9;r=3300;w=60;c=Customer Temp",
"content-security-policy": "default-src 'self';child-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://app.vinesign.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com https://app.pendo.io https://feedback.us.pendo.io docs.google.com https://feedback.filevine.com *.newrelic.com *.filev.io *.flvn.io filev.io flvn.io 'self';connect-src *.filevinedev.com *.filevineapp.com *.filevine.ca *.filevine.com *.filevinegov.com *.fvauth.com https://app.vinesign.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com *.nr-data.net *.pendo.io *.pdftron.com *.typeform.com *.newrelic.com https://app.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://localhost:8080 *.filev.io *.flvn.io filev.io flvn.io 'self' blob: wss:;font-src *.bootstrapcdn.com fonts.gstatic.com *.typekit.net *.typeform.com 'self' data: blob:;frame-src *;frame-ancestors https://*.filevineapp.com https://app.pendo.io 'self';img-src *.typekit.net *.typeform.com https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-globalproducts-prod-us-logos.s3.us-west-2.amazonaws.com https://us.fv-globalproducts-logos.prod.filevine.com https://fv-prod-us-shard-h-fv-internal-image.s3.amazonaws.com https://fv-prod-us-shard-h-fv-internal-image.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io *.kaywa.com www.googletagmanager.com 'self' data: blob: cid:;manifest-src 'self';media-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com https://us-shard-h-discussions.filevineapp.com *.filev.io *.flvn.io filev.io flvn.io 'self';object-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io 'self';script-src *.bootstrapcdn.com *.typekit.net *.typeform.com *.newrelic.com *.nr-data.net https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://code.jquery.com https://localhost:8080 https://www.googletagmanager.com 'unsafe-inline' 'unsafe-eval' 'self' blob:;style-src *.bootstrapcdn.com fonts.googleapis.com *.typekit.net *.typeform.com https://app.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://cdn.pendo.io https://data.pendo.io 'unsafe-inline' 'self';worker-src 'self' blob: 'unsafe-inline'",
"x-filevine-api-version": "3.3426.3.0",
"x-fv-correlation-id": "51ff3a6e96c24996b386a4177bef952a",
"x-aspnet-version": "4.0.30319",
"x-powered-by": "ASP.NET",
"x-content-type-options": "nosniff",
"x-frame-option": "SAMEORIGIN",
"x-xss-protection": "1; mode=block",
"x-fv-gateway-correlation-id": "51ff3a6e96c24996b386a4177bef952a",
"cf-cache-status": "DYNAMIC",
"Content-Encoding": "gzip",
"CF-RAY": "992d12c2ec4131ba-SEA",
"alt-svc": "h3=\":443\"; ma=86400"
}
}
}

442
examples/project_list.json Normal file
View File

@@ -0,0 +1,442 @@
{
"sample_request": {
"url": "https://api.filevineapp.com/fv-app/v2/projects",
"headers": {
"Accept": "application/json",
"Authorization": "Bearer eyJhbGciOiJSUzUxMiIsImtpZCI6Ijg2NjRFMkY0MDNCQjIxMzk2MzQ4NUFDOEI0MzVGMEJBOTgxNTBFN0RSUzUxMiIsInR5cCI6ImF0K2p3dCIsIng1dCI6ImhtVGk5QU83SVRsalNGckl0RFh3dXBnVkRuMCJ9.eyJuYmYiOjE3NjExNzg4NzQsImV4cCI6MTc2MTE4MDY3NCwiaXNzIjoiaHR0cHM6Ly9pZGVudGl0eS5maWxldmluZS5jb20iLCJhdWQiOlsiZmlsZXZpbmUudjIuYXBpIiwiZnYuYXBpLmdhdGV3YXkiLCJmdi5hdXRoIl0sImNsaWVudF9pZCI6IjRGMTg3MzhDLTEwN0EtNEI4Mi1CRkFDLTMwOEYxQjZBNjI2QSIsInN1YiI6ImY3MDQ4NGZmLTQ5MjItNDliMy05MWFkLTE2YjA5Mjk5MGIzMCIsImF1dGhfdGltZSI6MTc2MTE3ODg3NCwiaWRwIjoibG9jYWwiLCJwYXRfaWQiOiJmUkEwbHRoSnp5aTBBWU9ZS1F4WGVlckczUHc1MXEyMmh4cTlzbk4zL2V3PSIsInBhdF9uYW1lIjoiQnJ5Y2UgQ292ZXJ0IiwicGF0X3ZlcnNpb24iOiIxIiwidGVuYW50X2ZybiI6ImZybjpmaWxldmluZTp1cy1wcm9kOmZpbGV2aW5lLWFwcDo6OnRlbmFudFxcMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwidGVuYW50X2lkIjoiMGJlOGFhOGItZmEyOS00MjQ0LWI1YzItMDE5NzIzMWExNWY5IiwianRpIjoiODlCRTQzRDlDRTYwQkNFNzg4Mzc5OEE1M0VBQkYwQTAiLCJpYXQiOjE3NjExNzg4NzQsInNjb3BlIjpbImVtYWlsIiwiZmlsZXZpbmUudjIuYXBpLioiLCJmdi5hcGkuZ2F0ZXdheS5hY2Nlc3MiLCJmdi5hdXRoLnRlbmFudC5yZWFkIiwib3BlbmlkIiwidGVuYW50Il0sImFtciI6WyJwZXJzb25hbF9hY2Nlc3NfdG9rZW4iXX0.P5rPsrBwt0MQ7SmR9l0K0WXgTkqStXXbyYRYCmjPaBnJZjLljMLkpqZsFYDf3pyg6t9oZK_oa86sGa7H13SBIocWqV70hloqAVn1uinwzKxS1gv0xvUdTkd_uPgJZPvQwsu_Py4RrXcHkhtH8dqOGO_VFBFyYvt5RSNUpVtS6ofP0srbxoAOUEsYW-VhmKV8ugcdFzATgTx6k6gXyy1fTKoa_s7tVSFcOe-yPD7xpnixY3aqnYFaCSf5PYzd9l7iBPzfgjn0cFWnOjtxKSl_fxzQacJqOonrSPYdTlNf-F6h5E-yT-CQuVVWRplcbadEDvgMptTQjbprZHTMLfdn-w",
"x-fv-orgid": "9227",
"x-fv-userid": "100510"
},
"params": {},
"method": "GET"
},
"sample_response": {
"status_code": 200,
"json": {
"count": 8,
"offset": 0,
"limit": 50,
"hasMore": false,
"requestedFields": "*",
"items": [
{
"projectTypeCode": "EVICT",
"rootDocFolderId": {
"native": 138236604,
"partner": null
},
"phaseName": "Nonpayment File Review",
"phaseDate": "2025-10-22T15:23:59.89Z",
"clientName": "Twin Pines Apartments (Twin Pine LLC)",
"clientPictureURL": "/images/Default6eba3d0d-6227-44c3-b907-8311c3d4da82.png",
"clientPictureKey": "Default6eba3d0d-6227-44c3-b907-8311c3d4da82.png",
"clientPictureS3Url": "",
"firstPrimaryName": "Lani",
"firstPrimaryUsername": "lani2",
"isArchived": false,
"lastActivity": "2025-10-22T15:27:44.177Z",
"uniqueKey": "TwinPinesApartmentsZ15905506",
"projectOrClientName": "Twin Pines Apartments (Twin Pine LLC) v. Guy, #12, NP",
"hashtags": [],
"orgId": 9227,
"projectEmailAddress": "TwinPinesApartmentsZ15905506@rothbardlawgroup.filevineapp.com",
"createdDate": "2025-10-22T15:03:48.41Z",
"projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/15905506",
"projectId": {
"native": 15905506,
"partner": null
},
"projectTypeId": {
"native": 34111,
"partner": null
},
"clientId": {
"native": 43125866,
"partner": null
},
"projectName": "Twin Pines Apartments (Twin Pine LLC) v. Guy, #12, NP",
"phaseId": {
"native": 209436,
"partner": null
},
"links": {
"self": "/projects/15905506",
"projectType": "/projecttypes/34111",
"rootFolder": "/folders/138236604",
"client": "/contacts/43125866",
"contacts": "/projects/15905506/contacts"
}
},
{
"projectTypeCode": "EVICT",
"rootDocFolderId": {
"native": 138206532,
"partner": null
},
"phaseName": "Nonpayment File Review",
"phaseDate": "2025-10-21T14:42:31.18Z",
"clientName": "3900 Adeline",
"clientPictureURL": "/images/avatar-user-placeholder.png",
"clientPictureKey": "avatar-user-placeholder.png",
"clientPictureS3Url": "",
"firstPrimaryName": "Rebecca Hartzler",
"firstPrimaryUsername": "rebecca63",
"isArchived": false,
"lastActivity": "2025-10-21T15:29:19.773Z",
"uniqueKey": "AdelineZ15902340",
"projectOrClientName": "3900 Adeline (3900 Adeline, LLC) v. Patten, #323, NP",
"hashtags": [],
"orgId": 9227,
"projectEmailAddress": "AdelineZ15902340@rothbardlawgroup.filevineapp.com",
"createdDate": "2025-10-21T14:38:26.957Z",
"projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/15902340",
"projectId": {
"native": 15902340,
"partner": null
},
"projectTypeId": {
"native": 34111,
"partner": null
},
"clientId": {
"native": 43183162,
"partner": null
},
"projectName": "3900 Adeline (3900 Adeline, LLC) v. Patten, #323, NP",
"phaseId": {
"native": 209436,
"partner": null
},
"links": {
"self": "/projects/15902340",
"projectType": "/projecttypes/34111",
"rootFolder": "/folders/138206532",
"client": "/contacts/43183162",
"contacts": "/projects/15902340/contacts"
}
},
{
"projectTypeCode": "EVICT",
"rootDocFolderId": {
"native": 138179643,
"partner": null
},
"phaseName": "Nonpayment File Review",
"phaseDate": "2025-10-20T15:07:56.023Z",
"clientName": "Bates Motel (Bates Motel Group, LLC)",
"clientPictureURL": "/images/Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png",
"clientPictureKey": "Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png",
"clientPictureS3Url": "",
"firstPrimaryName": "Stephanie Gonzalez",
"firstPrimaryUsername": "stephanie148",
"isArchived": false,
"lastActivity": "2025-10-21T00:07:51.847Z",
"uniqueKey": "vFakeDefendantNPZ15901312",
"projectOrClientName": "v. Fake Defendant, #50, NP",
"hashtags": [],
"orgId": 9227,
"projectEmailAddress": "vFakeDefendantNPZ15901312@rothbardlawgroup.filevineapp.com",
"createdDate": "2025-10-20T14:31:07.29Z",
"projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/15901312",
"projectId": {
"native": 15901312,
"partner": null
},
"projectTypeId": {
"native": 34111,
"partner": null
},
"clientId": {
"native": 40576359,
"partner": null
},
"projectName": "v. Fake Defendant, #50, NP",
"phaseId": {
"native": 209436,
"partner": null
},
"links": {
"self": "/projects/15901312",
"projectType": "/projecttypes/34111",
"rootFolder": "/folders/138179643",
"client": "/contacts/40576359",
"contacts": "/projects/15901312/contacts"
}
},
{
"projectTypeCode": "EVICT",
"rootDocFolderId": {
"native": 138011554,
"partner": null
},
"phaseName": "Migrated",
"phaseDate": "2025-10-16T20:33:18.8Z",
"clientName": "ABC Property Management",
"clientPictureURL": "/images/Default5888c798-e92d-4c2e-82e9-737a564b5317.png",
"clientPictureKey": "Default5888c798-e92d-4c2e-82e9-737a564b5317.png",
"clientPictureS3Url": "",
"firstPrimaryName": "Brian Skarbek",
"firstPrimaryUsername": "brianskarbek",
"isArchived": false,
"lastActivity": "2025-10-16T20:33:18.803Z",
"uniqueKey": "TBDZ15896299",
"projectOrClientName": "TBD",
"hashtags": [],
"orgId": 9227,
"projectEmailAddress": "TBDZ15896299@rothbardlawgroup.filevineapp.com",
"createdDate": "2025-10-16T20:33:18.8Z",
"projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/15896299",
"projectId": {
"native": 15896299,
"partner": null
},
"projectTypeId": {
"native": 34111,
"partner": null
},
"clientId": {
"native": 43122010,
"partner": null
},
"projectName": "TBD",
"phaseId": {
"native": 211957,
"partner": null
},
"links": {
"self": "/projects/15896299",
"projectType": "/projecttypes/34111",
"rootFolder": "/folders/138011554",
"client": "/contacts/43122010",
"contacts": "/projects/15896299/contacts"
}
},
{
"projectTypeCode": "EVICT",
"rootDocFolderId": {
"native": 137923183,
"partner": null
},
"phaseName": "Default",
"phaseDate": "2025-10-22T18:03:38.6Z",
"clientName": "Bates Motel (Bates Motel Group, LLC)",
"clientPictureURL": "/images/Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png",
"clientPictureKey": "Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png",
"clientPictureS3Url": "",
"firstPrimaryName": "Sarra McDonald",
"firstPrimaryUsername": "sarra",
"isArchived": false,
"lastActivity": "2025-10-22T22:14:58.907Z",
"uniqueKey": "BatesMotelZ15892080",
"projectOrClientName": "Bates Motel (Bates Motel Group, LLC)",
"hashtags": [],
"orgId": 9227,
"projectEmailAddress": "BatesMotelZ15892080@rothbardlawgroup.filevineapp.com",
"createdDate": "2025-10-13T21:54:29.187Z",
"projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/15892080",
"projectId": {
"native": 15892080,
"partner": null
},
"projectTypeId": {
"native": 34111,
"partner": null
},
"clientId": {
"native": 40576359,
"partner": null
},
"projectName": "Bates Motel (Bates Motel Group, LLC)",
"phaseId": {
"native": 211435,
"partner": null
},
"links": {
"self": "/projects/15892080",
"projectType": "/projecttypes/34111",
"rootFolder": "/folders/137923183",
"client": "/contacts/40576359",
"contacts": "/projects/15892080/contacts"
}
},
{
"projectTypeCode": "EVICT",
"rootDocFolderId": {
"native": 128719223,
"partner": null
},
"phaseName": "Trial Prep and Trial",
"phaseDate": "2025-10-22T21:40:11.117Z",
"clientName": "1234 Main Street Apartments (Main Street Property Holding LLC)",
"clientPictureURL": "/images/avatar-user-placeholder.png",
"clientPictureKey": "avatar-user-placeholder.png",
"clientPictureS3Url": "",
"firstPrimaryName": "Sarra McDonald",
"firstPrimaryUsername": "sarra",
"isArchived": false,
"lastActivity": "2025-10-22T22:14:26.443Z",
"uniqueKey": "MainStreetApartmentsvSmithNTTZ12685677",
"projectOrClientName": "1234 Main Street Apartments v Smith, #24, NTT",
"hashtags": [
"#Attorney-to-accept-service"
],
"orgId": 9227,
"projectEmailAddress": "MainStreetApartmentsvSmithNTTZ12685677@rothbardlawgroup.filevineapp.com",
"createdDate": "2025-06-17T01:07:55.873Z",
"projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/12685677",
"projectId": {
"native": 12685677,
"partner": null
},
"projectTypeId": {
"native": 34111,
"partner": null
},
"clientId": {
"native": 40637221,
"partner": null
},
"projectName": "1234 Main Street Apartments v Smith, #24, NTT",
"phaseId": {
"native": 211438,
"partner": null
},
"links": {
"self": "/projects/12685677",
"projectType": "/projecttypes/34111",
"rootFolder": "/folders/128719223",
"client": "/contacts/40637221",
"contacts": "/projects/12685677/contacts"
}
},
{
"projectTypeCode": "EVICT",
"rootDocFolderId": {
"native": 128547693,
"partner": null
},
"phaseName": "Notice Pending",
"phaseDate": "2025-10-21T00:21:07.603Z",
"clientName": "Bates Motel (Bates Motel Group, LLC)",
"clientPictureURL": "/images/Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png",
"clientPictureKey": "Defaultf3f74f0b-0eee-4a40-98f1-57960d87f12a.png",
"clientPictureS3Url": "",
"firstPrimaryName": "Jen Rhodes",
"firstPrimaryUsername": "jen60",
"isArchived": false,
"lastActivity": "2025-10-21T00:07:49.373Z",
"uniqueKey": "BatesMotelVsTenantNameZ12662539",
"projectOrClientName": "Bates Motel Vs. Tenant Name 25CV123456",
"hashtags": [],
"orgId": 9227,
"projectEmailAddress": "BatesMotelVsTenantNameZ12662539@rothbardlawgroup.filevineapp.com",
"createdDate": "2025-06-06T02:48:45.96Z",
"projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/12662539",
"projectId": {
"native": 12662539,
"partner": null
},
"projectTypeId": {
"native": 34111,
"partner": null
},
"clientId": {
"native": 40576359,
"partner": null
},
"projectName": "Bates Motel Vs. Tenant Name 25CV123456",
"phaseId": {
"native": 209439,
"partner": null
},
"links": {
"self": "/projects/12662539",
"projectType": "/projecttypes/34111",
"rootFolder": "/folders/128547693",
"client": "/contacts/40576359",
"contacts": "/projects/12662539/contacts"
}
},
{
"projectTypeCode": "ADMIN",
"rootDocFolderId": {
"native": 128547692,
"partner": null
},
"phaseName": "ADMIN",
"phaseDate": "2025-06-06T02:47:33.87Z",
"clientName": "Admin",
"clientPictureURL": "/images/c8711333-fad7-40f3-81fb-2853b8a7e082.png",
"clientPictureKey": "c8711333-fad7-40f3-81fb-2853b8a7e082.png",
"clientPictureS3Url": "",
"firstPrimaryName": "Jen Rhodes",
"firstPrimaryUsername": "jen60",
"isArchived": false,
"lastActivity": "2025-06-06T02:47:33.873Z",
"uniqueKey": "VineskillsTrainingandResourcesProjectZ12662538",
"projectOrClientName": "Vineskills | Training and Resources Project",
"hashtags": [],
"orgId": 9227,
"projectEmailAddress": "VineskillsTrainingandResourcesProjectZ12662538@rothbardlawgroup.filevineapp.com",
"createdDate": "2025-06-06T02:47:33.87Z",
"projectUrl": "https://rothbardlawgroup.filevineapp.com/#/project/12662538",
"projectId": {
"native": 12662538,
"partner": null
},
"projectTypeId": {
"native": 34112,
"partner": null
},
"clientId": {
"native": 40576358,
"partner": null
},
"projectName": "Vineskills | Training and Resources Project",
"phaseId": {
"native": 209445,
"partner": null
},
"links": {
"self": "/projects/12662538",
"projectType": "/projecttypes/34112",
"rootFolder": "/folders/128547692",
"client": "/contacts/40576358",
"contacts": "/projects/12662538/contacts"
}
}
],
"links": {
"self": "/projects?name=&number=&createdSince=&searchterm=&projectId=&incidentDate=&hashtags=&offset=0&limit=50&requestedFields=*",
"prev": null,
"next": null
}
},
"headers": {
"Date": "Thu, 23 Oct 2025 00:21:15 GMT",
"Content-Type": "application/json; charset=utf-8",
"Transfer-Encoding": "chunked",
"Connection": "keep-alive",
"Server": "cloudflare",
"access-control-allow-headers": "Content-Type, x-fv-orgid, x-fv-clientip, x-fv-userid, authorization, x-fv-application",
"access-control-allow-methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS, LOCK, UNLOCK, PROPPATCH, PROPFIND",
"access-control-allow-origin": "*",
"Cache-Control": "no-store, must-revalidate, no-cache, max-age=0, private",
"ratelimit-limit": "10;r=3300;w=60;c=Customer Temp",
"ratelimit-remaining": "9;r=3300;w=60;c=Customer Temp",
"content-security-policy": "default-src 'self';child-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://app.vinesign.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com https://app.pendo.io https://feedback.us.pendo.io docs.google.com https://feedback.filevine.com *.newrelic.com *.filev.io *.flvn.io filev.io flvn.io 'self';connect-src *.filevinedev.com *.filevineapp.com *.filevine.ca *.filevine.com *.filevinegov.com *.fvauth.com https://app.vinesign.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.amazonaws.com *.nr-data.net *.pendo.io *.pdftron.com *.typeform.com *.newrelic.com https://app.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://localhost:8080 *.filev.io *.flvn.io filev.io flvn.io 'self' blob: wss:;font-src *.bootstrapcdn.com fonts.gstatic.com *.typekit.net *.typeform.com 'self' data: blob:;frame-src *;frame-ancestors https://*.filevineapp.com https://app.pendo.io 'self';img-src *.typekit.net *.typeform.com https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-globalproducts-prod-us-logos.s3.us-west-2.amazonaws.com https://us.fv-globalproducts-logos.prod.filevine.com https://fv-prod-us-shard-h-fv-internal-image.s3.amazonaws.com https://fv-prod-us-shard-h-fv-internal-image.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io *.kaywa.com www.googletagmanager.com 'self' data: blob: cid:;manifest-src 'self';media-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com https://us-shard-h-discussions.filevineapp.com *.filev.io *.flvn.io filev.io flvn.io 'self';object-src https://fv-prod-us-shard-h-images.s3.amazonaws.com https://fv-prod-us-shard-h-images.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-docs.s3.amazonaws.com https://fv-prod-us-shard-h-docs.s3.us-west-2.amazonaws.com https://fv-prod-us-shard-h-report-export.s3.us-west-2.amazonaws.com *.filev.io *.flvn.io filev.io flvn.io 'self';script-src *.bootstrapcdn.com *.typekit.net *.typeform.com *.newrelic.com *.nr-data.net https://app.pendo.io https://cdn.pendo.io https://data.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://code.jquery.com https://localhost:8080 https://www.googletagmanager.com 'unsafe-inline' 'unsafe-eval' 'self' blob:;style-src *.bootstrapcdn.com fonts.googleapis.com *.typekit.net *.typeform.com https://app.pendo.io https://pendo-static-5683967597215744.storage.googleapis.com https://pendo-io-static.storage.googleapis.com https://duuxdetkhlwyv.cloudfront.net https://cdn.pendo.io https://data.pendo.io 'unsafe-inline' 'self';worker-src 'self' blob: 'unsafe-inline'",
"x-filevine-api-version": "3.3426.3.0",
"x-fv-correlation-id": "4562cb54a936470cb3fb463db5837dab",
"x-aspnet-version": "4.0.30319",
"x-powered-by": "ASP.NET",
"x-content-type-options": "nosniff",
"x-frame-option": "SAMEORIGIN",
"x-xss-protection": "1; mode=block",
"x-fv-gateway-correlation-id": "4562cb54a936470cb3fb463db5837dab",
"cf-cache-status": "DYNAMIC",
"Content-Encoding": "gzip",
"CF-RAY": "992d12bd2939decc-SEA",
"alt-svc": "h3=\":443\"; ma=86400"
}
}
}

111
generate_sample.py Normal file
View File

@@ -0,0 +1,111 @@
import os
import json
import requests
from dotenv import load_dotenv
# Load environment variables from .env file
load_dotenv()
def get_bearer_token():
"""Request a bearer token using credentials from environment variables."""
url = "https://identity.filevine.com/connect/token"
data = {
"client_id": os.environ.get("FILEVINE_CLIENT_ID"),
"client_secret": os.environ.get("FILEVINE_CLIENT_SECRET"),
"grant_type": "personal_access_token",
"scope": "fv.api.gateway.access tenant filevine.v2.api.* email openid fv.auth.tenant.read",
"token": os.environ.get("FILEVINE_PERSONAL_ACCESS_TOKEN"),
}
headers = {"Accept": "application/json"}
response = requests.post(url, data=data, headers=headers)
response.raise_for_status()
return response.json().get("access_token")
# Define the collection of request/response pairs
# User can add more tuples following the same pattern
SAMPLE_PROJECT_ID = 15905506
REQUESTS = [
(
{
"url": "https://api.filevineapp.com/fv-app/v2/projects",
"headers": {
"Accept": "application/json",
"Authorization": f"Bearer {get_bearer_token()}",
"x-fv-orgid": os.environ.get("FILEVINE_ORG_ID"),
"x-fv-userid": os.environ.get("FILEVINE_USER_ID"),
},
"params": {},
"method": "GET"
},
"examples/project_list.json"
),
(
{
"url": f"https://api.filevineapp.com/fv-app/v2/Projects/{SAMPLE_PROJECT_ID}/contacts",
"headers": {
"Accept": "application/json",
"Authorization": f"Bearer {get_bearer_token()}",
"x-fv-orgid": os.environ.get("FILEVINE_ORG_ID"),
"x-fv-userid": os.environ.get("FILEVINE_USER_ID"),
},
"params": {},
"method": "GET"
},
"examples/project_contacts.json"
),
(
{
"url": f"https://api.filevineapp.com/fv-app/v2/contacts/43125866",
"headers": {
"Accept": "application/json",
"Authorization": f"Bearer {get_bearer_token()}",
"x-fv-orgid": os.environ.get("FILEVINE_ORG_ID"),
"x-fv-userid": os.environ.get("FILEVINE_USER_ID"),
},
"params": {},
"method": "GET"
},
"examples/client.json"
)
]
def generate_sample_request_response(request_config):
"""Generate a sample request/response pair using the provided request configuration."""
# Make the request
response = requests.request(**request_config)
# Create structured output
result = {
"sample_request": request_config,
"sample_response": {
"status_code": response.status_code,
"json": response.json() if response.content else None,
"headers": dict(response.headers)
}
}
return result
if __name__ == "__main__":
try:
# Ensure examples directory exists
os.makedirs("examples", exist_ok=True)
# Process each request/response pair
for request_config, output_path in REQUESTS:
# Regenerate bearer token for each request (tokens may expire)
request_config["headers"]["Authorization"] = f"Bearer {get_bearer_token()}"
result = generate_sample_request_response(request_config)
# Save pretty printed JSON to file
with open(output_path, 'w') as f:
json.dump(result, f, indent=2)
print(f"Generated {len(REQUESTS)} sample request/response pairs")
except Exception as e:
print(f"Error: {e}")

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
Flask==3.0.3
firebase-admin==6.6.0
python-dotenv==1.0.1
requests==2.32.3
itsdangerous==2.2.0

13
rothbard-service-account.json Executable file
View File

@@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "rothbard-3f496",
"private_key_id": "58d242c0d372e3ae4821e1b32e2d4be8ca718fde",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC5OuQyds3Gdvsw\n44ZJLekGrqt0ktj5YjSvfEjzk7jZuMJITy+mCpohfm6JUv6IRl1MHY+P8sxM9xiT\nkLF3EXeAyyWXVWx4KjpGX0KeG4IfSZhnMKljDBtZIyxJhOvy54+QBFcRqjIeeB5s\n5EHG2jJySOzDUA7JMzP/Oo0MmU332ylLU3NIutolccs1XC/ymi2ocHpgcGhENl40\nDLvn6ZMS3hEulkaUDfMBovJrvvjWWxbK4HLb+04m2+XD98uOmD3x3/nUWuiLNMH+\n1mJNzVRgiNFCDyqg3brUWP6eEWeF0W66QWPk7SV72lPsf8Ldq7BicS5p5k4gMxO2\ng+eaAkZxAgMBAAECggEAL+GOTZEyXiQxiJC4DMCmZQjP32F6XvTI47f/7573AKjm\n5+Q4T/abox7YmfzvOPDfeyaFDtPXhem126diiIHmX0+kFvuI/4MC71/+i3pW55mR\noNMOZkEh7KfP8e0/RNog3TyR+UoCjKfGTaWvbyTGN46sTUyrlcz7mvVasrAKXJBE\nLtyvz5BpXpikybC70MDY/bDKUxrl45AaRR/FuBTqDj7wK56O1TMaviRZUks6GHUM\nYphoammseQi1X/OjxwjtUHgclKcZN3r1F+tYdbBMGLbKGu8rRjEr8y8vLJAVxK0M\nSVsNzjlsUFGF/RVljb38tlqKXchZ+DJ3FZ3fyU4YFQKBgQD2FucwWnoQi0E0d9kX\nJWgmoH9fYwDr6NpD7k9bLW4ktrWTEENAzfV9/cfFRlFbHA5Zk35a4xKYY3ZXiO76\n0Xk5nkhWrymNd46yT094SInEugaKfBAw6Xo243Jq6/eR7GD1KSL+9z+4oCtQ+JHv\nepAiTBDGNOUuXQT06ONJrEUXPwKBgQDAsIx/rJdcaH0XZH+0TeNY0fe5U9LAdNZm\nkNeVSaMBY+UuRvZt0Y0C3F54ngXfEtmAwvQ4LVgBmJVv4JAkETRowAJeD2RflhYl\n4s7aaPyh0GJJ4/j8/XP0TZN5gv7D3MNJAh2bJLluONjZmWgckaRlU8E2za2EZSqY\nW2bmoFNmTwKBgAZLF6Z46d46cXRyDC83Wa6DND6wPXnK/qn2Ejl2s/ZkZchZBh9G\nJR0PvGgjIDmAQi2wQ+73F6amBITAj7wCV2NN1PPCjwF7KT8OIC4nTL6nMzufaJqX\nnfSBZI+vcSleLiyW3LpAgHSsQ+9SLAk/zSfTYipvd9zzrAjHW+iqaynpAoGAMjZP\nhn29O7Fm14+yz5N0aRLeEQdM5iYMMNIRu69isNwNPs/zK47txg8S9y+GrCjHUQx8\ng58dTd0rI+pK5XsuQxW2CDjOmTINN3YxHS06mBgrZMHpglOxwbntcj62kOeYZBAP\nEvyw7Y4WxC17ueYiBt2afeN/Ef8i6Gz5FaQ113UCgYBr39cTINpIuwDyfDAqu032\n0zP/8GFE5zb4VlGoKC2Ma/9x+nkOf05eXqv1jOpq+hsYlBe9mrufeF0gcKAycHWn\nW1QgkepPXlrvFXws928TQUGv+Jp63UMdNWXbfh4ZIp3vhAAdh+h8TcvRyVrADZgB\nqZXfdtkfYTQewM7p8vno3Q==\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fbsvc@rothbard-3f496.iam.gserviceaccount.com",
"client_id": "112093576377036658724",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40rothbard-3f496.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

2
static/auth.js Normal file
View File

@@ -0,0 +1,2 @@
(function(){
})();

28
templates/base.html Normal file
View File

@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ title or 'App' }}</title>
<!-- Tailwind (CDN for demo; consider self-hosting for prod) -->
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="min-h-screen bg-slate-50 text-slate-900">
<header class="border-b bg-white">
<div class="max-w-5xl mx-auto p-4 flex items-center justify-between">
<a href="/" class="font-semibold">Filevine Demo</a>
<nav class="space-x-4">
{% if session.uid %}
<a href="/dashboard" class="text-sm text-slate-600 hover:text-slate-900">Dashboard</a>
<a href="/logout" class="text-sm text-slate-600 hover:text-slate-900">Logout</a>
{% else %}
<a href="/login" class="text-sm text-slate-600 hover:text-slate-900">Login</a>
{% endif %}
</nav>
</div>
</header>
<main class="max-w-5xl mx-auto p-6">
{% block content %}{% endblock %}
</main>
</body>
</html>

45
templates/dashboard.html Normal file
View File

@@ -0,0 +1,45 @@
{% extends 'base.html' %}
{% block content %}
<h1 class="text-xl font-semibold mb-4">Projects for {{ case_email }}</h1>
<div class="bg-white shadow rounded-2xl overflow-hidden">
<table class="min-w-full">
<thead class="bg-slate-100 text-left text-sm">
<tr>
<th class="px-4 py-3">Project Email</th>
<th class="px-4 py-3">Matter Description</th>
<th class="px-4 py-3">Number</th>
<th class="px-4 py-3">Incident Date</th>
<th class="px-4 py-3">Name</th>
<th class="px-4 py-3">Link</th>
</tr>
</thead>
<tbody class="divide-y">
{% for r in rows %}
<tr class="hover:bg-slate-50">
<td class="px-4 py-3 text-sm">{{ r.client or '—' }}</td>
<td class="px-4 py-3 text-sm">{{ r.matter_description or '—' }}</td>
<td class="px-4 py-3 text-sm">
<ul>
{% for c in r.contacts %}
<li>{{ c.orgContact.firstName }}</li>
{% endfor %}
</ul>
</td>
<td class="px-4 py-3 text-sm">{{ r.Number or '—' }}</td>
<td class="px-4 py-3 text-sm">{{ (r.IncidentDate or '')[:10] }}</td>
<td class="px-4 py-3 text-sm">{{ r.ProjectName or '—' }}</td>
<td class="px-4 py-3 text-sm">
{% if r.projectUrl %}
<a class="text-blue-600 hover:underline" href="{{ r.projectUrl }}" target="_blank">Open</a>
{% else %}—{% endif %}
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="px-4 py-6 text-center text-slate-500">No matching projects found.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

69
templates/login.html Normal file
View File

@@ -0,0 +1,69 @@
{% extends 'base.html' %}
{% block content %}
<div class="max-w-md mx-auto bg-white shadow rounded-2xl p-6">
<h1 class="text-xl font-semibold mb-4">Sign in</h1>
<form id="login-form" class="space-y-3">
<div>
<label class="block text-sm mb-1">Email</label>
<input id="email" type="email" required class="w-full border rounded-lg p-2" />
</div>
<div>
<label class="block text-sm mb-1">Password</label>
<input id="password" type="password" required class="w-full border rounded-lg p-2" />
</div>
<button type="submit" class="w-full py-2 rounded-lg bg-slate-900 text-white">Sign in</button>
<p id="error" class="text-sm text-red-600 mt-2 hidden"></p>
</form>
</div>
<!-- Firebase App (the core Firebase SDK) -->
<script src="https://www.gstatic.com/firebasejs/12.4.0/firebase-app-compat.js"></script>
<!-- Firebase Auth -->
<script src="https://www.gstatic.com/firebasejs/12.4.0/firebase-auth-compat.js"></script>
<script>
window.FIREBASE_CONFIG = {{ firebase_config|tojson }};
// import { initializeApp } from 'https://www.gstatic.com/firebasejs/12.4.0/firebase-app.js'
// // If you enabled Analytics in your project, add the Firebase SDK for Google Analytics
// import { getAnalytics } from 'https://www.gstatic.com/firebasejs/12.4.0/firebase-analytics.js'
// // Add Firebase products that you want to use
// import { getAuth } from 'https://www.gstatic.com/firebasejs/12.4.0/firebase-auth.js'
// import { getFirestore } from 'https://www.gstatic.com/firebasejs/12.4.0/firebase-firestore.js'
const app = firebase.initializeApp(window.FIREBASE_CONFIG || {});
const auth = firebase.auth()
console.log(app)
console.log(auth)
console.log(auth.signInWithEmailAndPassword)
const form = document.getElementById('login-form');
const email = document.getElementById('email');
const password = document.getElementById('password');
const err = document.getElementById('error');
form.addEventListener('submit', async (e) => {
e.preventDefault();
err.classList.add('hidden');
try {
const cred = await auth.signInWithEmailAndPassword(email.value, password.value);
const idToken = await cred.user.getIdToken();
const res = await fetch('/session_login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ idToken })
});
if(!res.ok){
throw new Error('Session exchange failed');
}
window.location.href = '/';
} catch (e) {
err.textContent = e.message || 'Login failed';
err.classList.remove('hidden');
}
});
</script>
{% endblock %}

11
templates/welcome.html Normal file
View File

@@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block content %}
<div class="bg-white shadow rounded-2xl p-6">
<h1 class="text-xl font-semibold mb-2">Welcome!</h1>
<p class="text-slate-700">Your account is almost ready. An administrator will finish setup soon.</p>
<ul class="mt-4 list-disc pl-6 text-sm text-slate-700">
<li><strong>enabled</strong>: {{ 'true' if profile.enabled else 'false' }}</li>
<li><strong>caseEmail</strong>: {{ profile.caseEmail or '—' }}</li>
</ul>
</div>
{% endblock %}