PlanSync API Docs v1.0.0
Swagger UI ReDoc
PlanSync API Documentation

Upload files, read spreadsheet-based plan data, and manage touchpoints programmatically. Designed for internal tools, CRM integrations, workflow automation, and partner platforms like Salesforce.

📊
Spreadsheets
Read-only access to structured plan data — Client List, Fee Schedule, Plan Performance, and more.
📁
Files
Upload, download, list, and delete documents associated with your plans.
📝
Touchpoints
Create, read, update, and delete client interaction records.
🌐
Base URL: https://api.plansync.ai
Authentication

All API requests (except /health) require an API key in the X-API-Key header.

Header format
X-API-Key: psk_your_api_key_here

Getting your API key

Visit PlanSync
Open Settings
Navigate to Settings → API Docs
Generate Key
View existing keys or click Generate New Key

Key management

Multiple keysYou can create multiple API keys, each with a unique name
RevocationKeys can be revoked at any time — immediate and permanent
ExpirationAPI keys do not expire automatically
RotationCreate new key first, update integrations, then revoke old key
⚠️
Treat your API key like a password. Never expose it in frontend JavaScript, mobile apps, or public repositories. Store it in environment variables, secret managers, or Named Credentials (Salesforce).
Quick Start

Get your first successful API call in under two minutes.

Step 1 — Health check (no auth required)

Request
curl https://api.plansync.ai/health
Response
{ "status": "healthy", "timestamp": "2025-12-16T20:41:41.714889", "version": "1.0.0" }

Step 2 — List spreadsheets

curl -H "X-API-Key: your_api_key" \ https://api.plansync.ai/api/v1/spreadsheets

Step 3 — Fetch real data

curl -H "X-API-Key: your_api_key" \ "https://api.plansync.ai/api/v1/spreadsheets/Client%20List/data?year=2025"
If you see a JSON response with your plan data, you are connected successfully.
Core Concepts

Understand the key resources and how they work before diving into endpoints.

📊
Spreadsheets
Read-only structured datasets — Client List, Fee Schedule, Plan Performance, etc. Each contains rows with defined columns.
📁
Files
Uploaded documents (PDF, DOCX, XLSX, images). Max 50 MB. Organized by user account.
📝
Touchpoints
Client interaction records — meetings, calls, reviews. Each belongs to a company and reporting period.
📅
Year-scoped Data
The year query param selects the database table. reporting_year in the body is the stored value.
🔑
Automatic user identity: Your API key is linked to your account. All requests are scoped to your data — no user_id needed.
Common Workflows
📊 Read client data
List available spreadsheets: GET /api/v1/spreadsheets
Fetch schema: GET /api/v1/spreadsheets/{name}/schema?year=2025
Fetch data: GET /api/v1/spreadsheets/{name}/data?year=2025
Parse the data array in your application
📁 Upload a document
Prepare file (supported type, under 50 MB)
POST /api/v1/files/upload with file, company_name, document_type
Confirm with returned file_location and metadata
📝 Manage touchpoints
Create: POST /api/v1/touchpoints?year=2025
List: GET /api/v1/touchpoints?year=2025
Update: PUT /api/v1/touchpoints/{id}?year=2025
Delete: DELETE /api/v1/touchpoints/{id}?year=2025
Health Check
GET/health

Check if the API is running. No authentication required.

Example request

curl https://api.plansync.ai/health

Success response 200

{ "status": "healthy", "timestamp": "2025-12-16T20:41:41.714889", "version": "1.0.0" }
Spreadsheets
GET/api/v1/spreadsheets

Returns the names of all available spreadsheet types.

Example request

curl -H "X-API-Key: your_api_key" \ https://api.plansync.ai/api/v1/spreadsheets

Success response 200

{ "spreadsheets": [ "Plan Touchpoints", "Requirements Checklist", "Client List", "Plan Design and Elections", "Plan Performance", "Advisor Service Schedule", "Fee Schedule", "Fund Performance", "Prospecting", "Participants Table" ] }
GET/api/v1/spreadsheets/{sheet_name}/data

Fetch rows from a specific spreadsheet.

Parameters

NameInTypeRequiredDescription
sheet_namepathstringYesSpreadsheet name, URL-encode spaces
yearqueryintegerYesReporting year
rolequerystringNoFilter: admin, tpa editor, recordkeeper, advisor, plan sponsor
assigned_clientsquerystringNoJSON array of client names

Example request

curl -H "X-API-Key: your_api_key" \ "https://api.plansync.ai/api/v1/spreadsheets/{sheet_name}/data?year=2025"

Success response 200

{ "sheet_name": "Client List", "year": 2025, "columns": ["Company Name", "EIN", "Plan Type"], "data": [{ "Company Name": "Acme Corp", "EIN": "12-3456789", "Plan Type": "401(k)" }], "row_count": 1 }

Error responses

StatusWhen
400Invalid spreadsheet name
404No data table for the specified year
Files
POST/api/v1/files/upload

Upload a document to storage (multipart/form-data).

Request body (form-data)

FieldTypeRequiredDescription
filefileYesThe file to upload
company_namestringYesCompany name
document_typestringYesE.g. Plan Document, Amendment, Report
📎
Allowed types: .pdf .doc .docx .xls .xlsx .jpg .jpeg .png — Max 50 MB. Duplicate filenames overwrite.

Example request

curl -X POST -H "X-API-Key: your_api_key" \ -F "file=@document.pdf" \ -F "company_name=Acme Corp" \ -F "document_type=Plan Document" \ https://api.plansync.ai/api/v1/files/upload

Success response 200

{ "message": "File 'document.pdf' uploaded successfully with binary cache.", "file_location": "user@example.com/document.pdf", "metadata": { "filename": "document.pdf", "company_name": "Acme Corp", "document_type": "Plan Document", "upload_date": "2025-12-16T15:30:00.000000", "file_size": 1048576 } }

Error responses

StatusWhen
400Unsupported file type or exceeds 50 MB
GET/api/v1/files

List all files for the authenticated user.

Example request

curl -H "X-API-Key: your_api_key" \ https://api.plansync.ai/api/v1/files

Success response 200

{ "user_id": "user@example.com", "files": { "document.pdf": { "company_name": "Acme Corp", "document_type": "Plan Document", "upload_date": "2025-12-16T15:30:00", "file_size": 1048576, "file_type": "application/pdf" } }, "file_count": 1 }
GET/api/v1/files/{filename}

Download a file from storage.

Parameters

NameInTypeRequiredDescription
filenamepathstringYesExact filename from file list

Example request

curl -H "X-API-Key: your_api_key" -o downloaded.pdf \ "https://api.plansync.ai/api/v1/files/{filename}"

Returns binary file stream with Content-Disposition: attachment.

DELETE/api/v1/files/{filename}

Permanently delete a file.

Example request

curl -X DELETE -H "X-API-Key: your_api_key" \ "https://api.plansync.ai/api/v1/files/{filename}"

Success response 200

{ "message": "File 'document.pdf' deleted successfully" }
Touchpoints

Client interaction records. All endpoints require a year query parameter. Each touchpoint has a UUID (unid) used for update and delete.

POST/api/v1/touchpoints?year={year}

Create a new touchpoint record.

Request body (JSON)

FieldTypeRequiredDescription
company_namestringYesCompany/client name
touchpointsstringYesDescription of interaction
reporting_quarterstringYesQ1, Q2, Q3, Q4, or All
reporting_yearstringYesYear value, e.g. "2025"
stakeholdersstringNoPlan Sponsor, TPA, Recordkeeper, etc.
datestringNoYYYY-MM-DD (defaults to today)
time_spentnumberNoMinutes spent
include_in_annual_review_reportstringNo"Yes" or "No" (default "No")

Example request

curl -X POST -H "X-API-Key: your_api_key" \ -H "Content-Type: application/json" \ -d '{"company_name":"Acme Corp","touchpoints":"Quarterly review","reporting_quarter":"Q4","reporting_year":"2025","stakeholders":"Plan Sponsor","time_spent":45}' \ "https://api.plansync.ai/api/v1/touchpoints?year=2025"

Success response 200

{ "message": "Touchpoint added successfully", "touchpoint": { "Company Name": "Acme Corp", "Touchpoints": "Quarterly review", "Reporting Quarter": "Q4", "Reporting Year": "2025", "Date": "2025-12-16", "Time Spent": 45.0, "Include In Annual Review Report": "No", "Stakeholders": "Plan Sponsor" }, "table_name": "touchpoints", "unid": "550e8400-e29b-41d4-a716-446655440000" }
💡
The unid in the response is the ID you use for update and delete operations.
GET/api/v1/touchpoints?year={year}

Retrieve touchpoints with optional filtering.

Query parameters

NameTypeRequiredDescription
yearintegerYesReporting year table
company_namestringNoFilter by company (exact match)
quarterstringNoQ1, Q2, Q3, Q4
stakeholderstringNoFilter by stakeholder

Example request

curl -H "X-API-Key: your_api_key" \ "https://api.plansync.ai/api/v1/touchpoints?year=2025&company_name=Acme%20Corp"

Success response 200

{ "year": 2025, "touchpoints": [{ "id": "ccd9565d-...", "Company Name": "Acme Corp", "Touchpoints": "Quarterly review", "Reporting Quarter": "Q4", "Date": "2025-12-16", "Time Spent": 45.0, "Stakeholders": "Plan Sponsor", "unid": "550e8400-e29b-41d4-a716-446655440000" }], "count": 1, "filters": { "company_name": "Acme Corp", "quarter": null, "stakeholder": null } }
PUT/api/v1/touchpoints/{touchpoint_id}?year={year}

Update an existing touchpoint. Only include fields you want to change.

Example request

curl -X PUT -H "X-API-Key: your_api_key" \ -H "Content-Type: application/json" \ -d '{"touchpoints":"Updated: Follow-up call","time_spent":30}' \ "https://api.plansync.ai/api/v1/touchpoints/{touchpoint_id}?year=2025"

Success response 200

{ "message": "Touchpoint updated successfully", "touchpoint_id": "550e8400-..." }
DELETE/api/v1/touchpoints/{touchpoint_id}?year={year}

Permanently delete a touchpoint.

Example request

curl -X DELETE -H "X-API-Key: your_api_key" \ "https://api.plansync.ai/api/v1/touchpoints/{touchpoint_id}?year=2025"

Success response 200

{ "message": "Touchpoint deleted successfully", "touchpoint_id": "550e8400-..." }
Errors & Limits

Error format

{ "detail": "Human-readable error message" }

Validation errors (422) include field-level details in detail as an array.

HTTP status codes

StatusMeaningWhen
200SuccessRequest completed
400Bad RequestInvalid parameters or values
401UnauthorizedMissing or invalid API key
403ForbiddenAccess denied
404Not FoundResource doesn't exist
422Unprocessable EntityValidation failure
429Too Many RequestsRate limit exceeded
500Server ErrorUnexpected failure

Rate limits & constraints

ConstraintValue
Rate limit60 requests/minute per API key (rolling window)
File size50 MB maximum
File types.pdf, .doc, .docx, .xls, .xlsx, .jpg, .jpeg, .png
Timeout120 seconds server-side
PaginationNot supported — all records returned in one response

Retry guidance

StatusRetry?Strategy
429YesWait 60s, then retry
500YesExponential backoff (1s, 2s, 4s — max 3 tries)
4xxNoFix the request
Integration Guides

Complete examples for Python, JavaScript, and Salesforce. See the full API reference for all endpoints.

Pythonimport requests, os API_KEY = os.environ.get("PLANSYNC_API_KEY") BASE = "https://api.plansync.ai" headers = {"X-API-Key": API_KEY} # List spreadsheets r = requests.get(f"{BASE}/api/v1/spreadsheets", headers=headers) print(r.json()) # Get client data r = requests.get(f"{BASE}/api/v1/spreadsheets/Client%20List/data", headers=headers, params={"year": 2025}) print(f"Found {r.json()['row_count']} clients") # Upload a file with open("doc.pdf", "rb") as f: r = requests.post(f"{BASE}/api/v1/files/upload", headers=headers, files={"file": f}, data={"company_name": "Acme Corp", "document_type": "Plan Document"}) print(r.json()) # Create a touchpoint r = requests.post(f"{BASE}/api/v1/touchpoints", headers=headers, params={"year": 2025}, json={"company_name": "Acme Corp", "touchpoints": "Quarterly review", "reporting_quarter": "Q4", "reporting_year": "2025", "stakeholders": "Plan Sponsor", "time_spent": 30}) print(f"Created: {r.json()['unid']}")
JavaScriptconst API_KEY = process.env.PLANSYNC_API_KEY; const BASE = 'https://api.plansync.ai'; const headers = { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' }; // Get spreadsheet data const res = await fetch( `${BASE}/api/v1/spreadsheets/Client%20List/data?year=2025`, { headers }); const data = await res.json(); console.log(`Found ${data.row_count} clients`); // Create a touchpoint const tpRes = await fetch(`${BASE}/api/v1/touchpoints?year=2025`, { method: 'POST', headers, body: JSON.stringify({ company_name: 'Acme Corp', touchpoints: 'Quarterly review', reporting_quarter: 'Q4', reporting_year: '2025', stakeholders: 'Plan Sponsor', time_spent: 30 }) }); const tp = await tpRes.json(); console.log('Created:', tp.unid);

Salesforce integration uses Named Credentials for secure API key storage. Never hardcode keys in Apex.

Remote Site Settings
Setup → Security → Remote Site Settings → add https://api.plansync.ai
Named Credential
Setup → Named Credentials → create with X-API-Key custom header
Apex Service Class
Create a reusable service using callout:PlanSync
Apexpublic class PlanSyncService { private static final String CRED = 'callout:PlanSync'; private static HttpResponse send(String method, String path, String body) { HttpRequest req = new HttpRequest(); req.setEndpoint(CRED + path); req.setMethod(method); req.setHeader('Content-Type', 'application/json'); req.setTimeout(120000); if (body != null) req.setBody(body); HttpResponse res = new Http().send(req); if (res.getStatusCode() >= 400) throw new PlanSyncException('Error ' + res.getStatusCode()); return res; } public static List<Object> getClientList(Integer year) { HttpResponse res = send('GET', '/api/v1/spreadsheets/Client%20List/data?year=' + year, null); Map<String, Object> r = (Map<String, Object>) JSON.deserializeUntyped(res.getBody()); return (List<Object>) r.get('data'); } public static String createTouchpoint(Map<String, Object> tp, Integer yr) { HttpResponse res = send('POST', '/api/v1/touchpoints?year=' + yr, JSON.serialize(tp)); Map<String, Object> r = (Map<String, Object>) JSON.deserializeUntyped(res.getBody()); return (String) r.get('unid'); } public class PlanSyncException extends Exception {} }
⚠️
Governor Limits: 100 callouts/transaction, 120s timeout, 12 MB response. See full Salesforce guide in the markdown docs.
FAQ & Troubleshooting
Why am I getting 401 Unauthorized?
Your API key is missing, invalid, or revoked. Check the X-API-Key header and confirm the key is active at app.plansync.ai.
Why am I getting 429 Too Many Requests?
You exceeded the 60 requests/minute rate limit. Wait 60 seconds and retry. For bulk operations, add delays between requests.
Why is my sheet name not working?
Sheet names must match exactly, including case and spaces. URL-encode spaces as %20. Call GET /api/v1/spreadsheets to see the exact names.
What does the year parameter control?
It selects the year-specific data table. ?year=2025 reads from the 2025 reporting table. The reporting_year body field is the stored value on the record.
What happens if I upload a file with the same name?
The existing file is overwritten. Metadata is updated to reflect the new upload.
Is the API data paginated?
No. All matching records are returned in a single response. Plan for potentially large payloads.
What is the difference between touchpoint_id and unid?
They're the same UUID. The URL parameter is called touchpoint_id, and the same value appears as unid in response objects.
Can I use the API from frontend JavaScript?
Technically yes, but never expose your API key in client-side code. Use a backend proxy instead.
Changelog
v1.0.0 Current
  • Initial public API release
  • Spreadsheet data access (read-only)
  • File upload, download, list, and delete
  • Touchpoint CRUD operations
  • API key authentication with rate limiting
  • Automatic user identity from API key

Versioning policy