Data API Reference
Integrate 3PM Auth data with your external systems. Access users, tenants, subscriptions, and members via a secure API key-authenticated REST API.
Overview
The Data API provides access to 3PM Auth data for external integrations. Use it to sync user data, tenant information, and subscription details with your CRM, analytics platform, or any other business system. You can also import users from external applications for seamless migrations.
Users
Access user profiles, emails, and authentication providers.
Tenants
Retrieve organization details, member counts, and settings.
Subscriptions
Query tenant app subscriptions and their status.
Members
Get tenant membership data with roles and app assignments.
Key Features
Authentication
All Data API endpoints require authentication via an API key. Include the key in the X-API-Key header.
API Key Format
X-API-Key: 3pm_abc123def456.xyzSecretKey789...
The API key consists of two parts separated by a dot:
- Key ID (
3pm_abc123def456) - Public identifier shown in dashboard - Secret (
xyzSecretKey789...) - Only shown once when created
Example Request
curl -X GET "https://idp.3pm.app/api/data/users" \-H "X-API-Key: 3pm_abc123.yourSecretKeyHere"
Creating API Keys
Select Scopes
Choose the permissions your integration needs:
| Scope | Access |
|---|---|
| users:read | Read user profiles and account information |
| users:write | Import/create users (for migrations) |
| tenants:read | Read tenant/organization details |
| subscriptions:read | Read tenant app subscriptions |
| members:read | Read tenant membership data |
Save Your API Key
Copy and store the API key securely. It will only be shown once.
# External IntegrationDATA_API_KEY=3pm_abc123.yourSecretKeyHere
Users API
/api/data/usersList all users with pagination and optional filtering.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| updatedSince | ISO 8601 | Filter users updated after this date |
| page | number | Page number (default: 1) |
| limit | number | Items per page (default: 50, max: 100) |
Example Request
curl "https://idp.3pm.app/api/data/users?updatedSince=2026-01-01T00:00:00Z&limit=50" \-H "X-API-Key: 3pm_abc123.secret"
Response
{"data": {"data": [{"id": "507f1f77bcf86cd799439011","firstName": "John","lastName": "Doe","email": "john@example.com","mobile": "+1234567890","profilePicUrl": "https://example.com/avatar.jpg","providers": [{ "provider": "google", "providerId": "123456789" }],"lastLoginAt": "2026-01-15T10:30:00.000Z","createdAt": "2026-01-01T00:00:00.000Z","updatedAt": "2026-01-15T10:30:00.000Z"}],"pagination": {"page": 1,"limit": 50,"total": 150,"hasMore": true},"meta": {"updatedSince": "2026-01-01T00:00:00Z","requestedAt": "2026-01-23T12:00:00.000Z"}},"error": null}
/api/data/users/:idGet a single user by ID, including their tenant memberships.
Response
{"data": {"data": {"id": "507f1f77bcf86cd799439011","firstName": "John","lastName": "Doe","email": "john@example.com","tenantMemberships": [{"tenantId": "60d5ecf4f1b5a23456789012","role": "admin","assignedApps": ["3pm_app1", "3pm_app2"],"joinedAt": "2026-01-05T00:00:00.000Z"}]}},"error": null}
/api/data/usersImport a single user from an external system. Requires users:write scope. Imported users will need to verify their email via OTP on first login.
status field in the response.Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| string | Yes | User's email address | |
| firstName | string | Yes | User's first name |
| lastName | string | Yes | User's last name |
| mobile | string | No | Phone number (E.164 format) |
| profilePicUrl | string | No | URL to profile picture |
| externalId | string | No | Your system's user ID (for reference) |
Example Request
curl -X POST "https://idp.3pm.app/api/data/users" \-H "X-API-Key: 3pm_abc123.secret" \-H "Content-Type: application/json" \-d '{"email": "john@example.com","firstName": "John","lastName": "Doe","mobile": "+1234567890","externalId": "usr_12345"}'
Success Response (201 Created)
{"data": {"id": "507f1f77bcf86cd799439011","email": "john@example.com","firstName": "John","lastName": "Doe","mobile": "+1234567890","externalId": "usr_12345","status": "created","createdAt": "2026-01-24T12:00:00.000Z"},"error": null}
Duplicate Skipped Response (200 OK)
{"data": {"email": "john@example.com","status": "skipped","reason": "Email already exists","existingUserId": "507f1f77bcf86cd799439011"},"error": null}
/api/data/users/importBulk import up to 100 users at once. Requires users:write scope. Ideal for migrating users from another authentication system.
Request Body
{"users": [{"email": "user1@example.com","firstName": "John","lastName": "Doe","mobile": "+1234567890","externalId": "usr_001"},{"email": "user2@example.com","firstName": "Jane","lastName": "Smith","externalId": "usr_002"}]}
Example Request
curl -X POST "https://idp.3pm.app/api/data/users/import" \-H "X-API-Key: 3pm_abc123.secret" \-H "Content-Type: application/json" \-d '{"users": [{ "email": "user1@example.com", "firstName": "John", "lastName": "Doe" },{ "email": "user2@example.com", "firstName": "Jane", "lastName": "Smith" }]}'
Response (200 OK)
{"data": {"summary": {"total": 2,"created": 1,"skipped": 1,"failed": 0},"results": [{"email": "user1@example.com","id": "507f1f77bcf86cd799439011","status": "created","externalId": "usr_001"},{"email": "user2@example.com","status": "skipped","reason": "Email already exists","id": "507f1f77bcf86cd799439012","externalId": "usr_002"}],"meta": {"requestedAt": "2026-01-24T12:00:00.000Z"}},"error": null}
Migration Best Practices
- Batch size: Send up to 100 users per request for optimal performance
- External IDs: Include your system's user IDs in
externalIdto map users after import - Error handling: Check each result's
statusfield (created, skipped, or failed) - User notification: Inform imported users they'll need to verify their email on first login
Tenants API
/api/data/tenantsList all tenants with member counts.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| updatedSince | ISO 8601 | Filter tenants updated after this date |
| page | number | Page number (default: 1) |
| limit | number | Items per page (default: 50, max: 100) |
Response
{"data": {"data": [{"id": "60d5ecf4f1b5a23456789012","name": "Acme Corporation","slug": "acme-corp","ownerId": "507f1f77bcf86cd799439011","memberCount": 15,"createdAt": "2026-01-01T00:00:00.000Z","updatedAt": "2026-01-20T00:00:00.000Z"}],"pagination": { "page": 1, "limit": 50, "total": 10, "hasMore": false }},"error": null}
/api/data/tenants/:idGet a single tenant with full member list and subscriptions.
Response
{"data": {"data": {"id": "60d5ecf4f1b5a23456789012","name": "Acme Corporation","slug": "acme-corp","owner": {"id": "507f1f77bcf86cd799439011","firstName": "Jane","lastName": "Smith","email": "jane@acme.com"},"members": [{"userId": "507f1f77bcf86cd799439011","role": "owner","assignedApps": ["3pm_app1"],"joinedAt": "2026-01-01T00:00:00.000Z","user": { "firstName": "Jane", "lastName": "Smith", "email": "jane@acme.com" }}],"subscriptions": [{"clientId": "3pm_app1","status": "active","subscribedAt": "2026-01-01T00:00:00.000Z"}],"memberCount": 15}},"error": null}
Subscriptions API
/api/data/subscriptionsList tenant app subscriptions with filtering options.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| tenantId | string | Filter by tenant ID |
| clientId | string | Filter by application client ID |
| status | string | "active" or "suspended" |
Response
{"data": {"data": [{"id": "subscription_id","tenantId": "60d5ecf4f1b5a23456789012","tenant": { "name": "Acme Corporation", "slug": "acme-corp" },"clientId": "3pm_app1","application": { "name": "My App" },"status": "active","subscribedAt": "2026-01-01T00:00:00.000Z"}],"pagination": { "page": 1, "limit": 50, "total": 25, "hasMore": false }},"error": null}
Members API
/api/data/membersList tenant memberships with user details.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
| updatedSince | ISO 8601 | Filter members updated after this date |
| tenantId | string | Filter by tenant ID |
| role | string | "owner", "admin", or "member" |
Response
{"data": {"data": [{"id": "member_id","tenantId": "60d5ecf4f1b5a23456789012","tenant": { "name": "Acme Corporation", "slug": "acme-corp" },"userId": "507f1f77bcf86cd799439011","user": { "firstName": "John", "lastName": "Doe", "email": "john@example.com" },"role": "admin","assignedApps": ["3pm_app1", "3pm_app2"],"joinedAt": "2026-01-05T00:00:00.000Z","updatedAt": "2026-01-20T00:00:00.000Z"}],"pagination": { "page": 1, "limit": 50, "total": 100, "hasMore": true }},"error": null}
Pagination & Filtering
Pagination
All list endpoints support pagination with page and limit parameters.
# Get page 2 with 25 items per pagecurl "https://idp.3pm.app/api/data/users?page=2&limit=25" \-H "X-API-Key: 3pm_abc123.secret"
Incremental Sync with updatedSince
Use updatedSince to fetch only records updated after a specific time. This is ideal for syncing data to external systems.
# Get users updated in the last 24 hourscurl "https://idp.3pm.app/api/data/users?updatedSince=2026-01-22T00:00:00Z" \-H "X-API-Key: 3pm_abc123.secret"
requestedAt timestamp from the response and use it as updatedSince for your next sync.Rate Limiting
Data API endpoints are rate limited to prevent abuse. The current limits are:
100 requests per minute
Per API key
Rate Limit Headers
Every response includes rate limit information in the headers:
X-RateLimit-Limit: 100X-RateLimit-Remaining: 95X-RateLimit-Reset: 60
Handling Rate Limits
When rate limited, you'll receive a 429 response. Implement exponential backoff:
async function fetchWithRetry(url, options, maxRetries = 3) {for (let i = 0; i < maxRetries; i++) {const response = await fetch(url, options);if (response.status === 429) {const resetIn = response.headers.get('X-RateLimit-Reset') || 60;console.log(`Rate limited. Waiting ${resetIn} seconds...`);await new Promise(resolve => setTimeout(resolve, resetIn * 1000));continue;}return response;}throw new Error('Max retries exceeded');}
Error Handling
Error Responses
| Status | Error | Description |
|---|---|---|
| 400 | Bad Request | Invalid parameters (e.g., invalid date format) |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | API key lacks required scope |
| 404 | Not Found | Resource not found |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Internal server error |
Error Response Format
{"data": null,"error": "Insufficient permissions. Required scope: users:read"}
Code Examples
Node.js / TypeScript
const API_URL = 'https://idp.3pm.app';const API_KEY = process.env.DATA_API_KEY!;interface SyncResult {users: any[];lastSync: string;}async function syncUsers(lastSync?: string): Promise<SyncResult> {const url = new URL(`${API_URL}/api/data/users`);if (lastSync) {url.searchParams.set('updatedSince', lastSync);}url.searchParams.set('limit', '100');const users: any[] = [];let page = 1;let hasMore = true;while (hasMore) {url.searchParams.set('page', page.toString());const response = await fetch(url, {headers: { 'X-API-Key': API_KEY },});if (!response.ok) {throw new Error(`API error: ${response.status}`);}const result = await response.json();users.push(...result.data.data);hasMore = result.data.pagination.hasMore;page++;}return {users,lastSync: new Date().toISOString(),};}// Usageconst { users, lastSync } = await syncUsers('2026-01-22T00:00:00Z');console.log(`Synced ${users.length} users`);
Python
import osimport requestsfrom datetime import datetimeAPI_URL = 'https://idp.3pm.app'API_KEY = os.environ['DATA_API_KEY']def sync_users(updated_since=None):headers = {'X-API-Key': API_KEY}params = {'limit': 100, 'page': 1}if updated_since:params['updatedSince'] = updated_sinceusers = []has_more = Truewhile has_more:response = requests.get(f'{API_URL}/api/data/users',headers=headers,params=params)response.raise_for_status()data = response.json()['data']users.extend(data['data'])has_more = data['pagination']['hasMore']params['page'] += 1return users, datetime.utcnow().isoformat() + 'Z'# Usageusers, last_sync = sync_users('2026-01-22T00:00:00Z')print(f'Synced {len(users)} users')
cURL Examples
# List all userscurl "https://idp.3pm.app/api/data/users" \-H "X-API-Key: 3pm_abc123.secret"# Get users updated since yesterdaycurl "https://idp.3pm.app/api/data/users?updatedSince=2026-01-22T00:00:00Z" \-H "X-API-Key: 3pm_abc123.secret"# Get a specific tenant with memberscurl "https://idp.3pm.app/api/data/tenants/60d5ecf4f1b5a23456789012" \-H "X-API-Key: 3pm_abc123.secret"# Get active subscriptions for a tenantcurl "https://idp.3pm.app/api/data/subscriptions?tenantId=60d5ecf4f1b5a23456789012&status=active" \-H "X-API-Key: 3pm_abc123.secret"# Get all admins across tenantscurl "https://idp.3pm.app/api/data/members?role=admin" \-H "X-API-Key: 3pm_abc123.secret"
Data API v1.0 - January 2026