The ClientPress REST API lets you read portal data and automate portal creation from external systems — WooCommerce, SureCart, Zapier, n8n, custom scripts, or any HTTP client.
Base URL: https://your-site.com/wp-json/cp/v1
Quick Reference #
GET /portals— App Password — List accessible portalsPOST /portals— App Password (admin) — Create a portalGET /portals/{id}— App Password — Get a single portalGET /portals/{id}/tasks— App Password — Get task lists and tasksGET /portals/{id}/files— App Password — Get file recordsGET /portals/{id}/deliverables— App Password — Get deliverable recordsGET /portals/{id}/activity— App Password — Get activity logPOST /portals/{id}/invite— App Password (admin) — Send a client invitationPOST /webhook— API Key — Inbound automation actionsGET /verify— API Key — Test API key connection
Authentication #
ClientPress supports two authentication schemes depending on the use case.
WordPress Application Passwords (REST API) #
Used for all /portals endpoints. Available on all WordPress 5.6+ installs with SSL enabled.
Creating an Application Password:
- In WordPress admin go to Users → Your Profile (or any user’s profile if you’re an admin).
- Scroll to Application Passwords.
- Enter a name (e.g. Zapier Integration) and click Add New Application Password.
- Copy the generated password — it will not be shown again.
Making authenticated requests — pass credentials using HTTP Basic Auth:
curl https://your-site.com/wp-json/cp/v1/portals \
-u "admin:abcd efgh ijkl mnop qrst uvwx"
const credentials = btoa('admin:abcdefghijklmnopqrstuvwx');
fetch('https://your-site.com/wp-json/cp/v1/portals', {
headers: { 'Authorization': `Basic ${credentials}` }
});
API Keys (Inbound Webhooks) #
Used for the /webhook and /verify endpoints. Generated from Portals → Integrations in WP admin. Pass the key in the X-CP-API-Key header — no WP user account required.
curl https://your-site.com/wp-json/cp/v1/verify \
-H "X-CP-API-Key: your-api-key"
See docs/inbound-webhooks.md for full details.
Access Levels #
manage_options(admin) — All portals; all write endpointscp_manager(project manager) — Assigned portals only; read-only via RESTcp_client(client) — Assigned portals only; read-only via REST
Project managers are WP users with the cp_manager role, assigned to specific portals from the portal edit screen. They have the same read access as clients via the REST API.
Endpoints #
List Portals #
GET /cp/v1/portals
Returns all portals the authenticated user can access. Admins receive all portals; clients and project managers receive only their assigned portals.
Response 200 OK:
[
{
"id": 42,
"title": "Acme Corp",
"slug": "acme-corp",
"url": "https://your-site.com/client-portal/acme-corp/",
"status": "active",
"accent_color": "#0073aa",
"allow_uploads": true,
"client_user_id": 7,
"sub_client_ids": [8, 9],
"manager_ids": [12],
"template_id": 3,
"template_applied": "2024-01-15 10:30:00",
"created_at": "2024-01-10 09:00:00"
}
]
Get Portal #
GET /cp/v1/portals/{id}
Returns a single portal by ID.
Path parameters:
id(integer) — Portal post ID
Response 200 OK: Same shape as a single item from List Portals.
Portal object field reference:
id(integer) — Portal post IDtitle(string) — Portal nameslug(string) — URL slugurl(string) — Full portal URLstatus(string) —active|pending|archivedaccent_color(string | null) — Hex colour, ornullif unsetallow_uploads(boolean) — Whether the client can upload filesclient_user_id(integer | null) — Primary client WP user ID, ornullsub_client_ids(integer[]) — Additional client WP user IDsmanager_ids(integer[]) — Assigned project manager WP user IDstemplate_id(integer | null) — Last applied portal template ID, ornulltemplate_applied(string | null) — Datetime of last template application, ornullcreated_at(string) — Portal creation datetime (YYYY-MM-DD HH:MM:SS)
Error responses:
403 Forbidden— Authenticated user does not have access to this portal404 Not Found— No portal exists with this ID
Get Tasks #
GET /cp/v1/portals/{id}/tasks
Returns all task lists and their tasks for the portal. Tasks include assignee user IDs.
Response 200 OK:
[
{
"id": 1,
"name": "Onboarding",
"sort_order": 0,
"created_at": "2024-01-10 09:00:00",
"tasks": [
{
"id": 10,
"title": "Sign the contract",
"description": "",
"status": "complete",
"due_date": "2024-01-20",
"assignee_ids": [7],
"sort_order": 0,
"created_at": "2024-01-10 09:00:00",
"updated_at": "2024-01-18 14:22:00"
},
{
"id": 11,
"title": "Schedule kickoff call",
"description": "Use the Calendly link in your welcome email.",
"status": "open",
"due_date": null,
"assignee_ids": [],
"sort_order": 1,
"created_at": "2024-01-10 09:00:00",
"updated_at": "2024-01-10 09:00:00"
}
]
}
]
Field reference:
status(string) —open|up_next|in_progress|completedue_date(string | null) —YYYY-MM-DDornullassignee_ids(integer[]) — WP user IDs
Get Files #
GET /cp/v1/portals/{id}/files
Returns file records for the portal. Visibility matches the portal UI: pending and rejected files are only visible to the uploader, designated approver, and admins.
Response 200 OK:
[
{
"name": "contract-q1-2024.pdf",
"original": "Contract Q1 2024.pdf",
"size": 204800,
"type": "application/pdf",
"url": "https://your-site.com/cp-file/42/contract-q1-2024.pdf",
"uploaded_by": 1,
"uploaded_at": "2024-01-10 09:00:00",
"approval_status": "approved",
"approver_id": null,
"rejection_note": null,
"approved_at": "2024-01-11 10:15:00"
}
]
Field reference:
name(string) — Internal filename (URL-safe)original(string) — Display name shown in the portalsize(integer) — File size in bytes (0for linked files)type(string) — MIME type (empty string for linked files)url(string) — Authenticated serving URL (see note below)uploaded_by(integer) — WP user ID of the uploaderuploaded_at(string) — Upload datetimeapproval_status(string) —none|pending|approved|rejectedapprover_id(integer | null) — WP user ID of the designated approver, ornullrejection_note(string | null) — Rejection message from the approver, ornullapproved_at(string | null) — Approval/rejection datetime, ornull
File URLs point to the authenticated serving endpoint (
/cp-file/{portal_id}/{filename}). A logged-in portal member session is required — these URLs are not publicly accessible.
Get Deliverables #
GET /cp/v1/portals/{id}/deliverables
Returns all deliverable records for the portal. Available to all portal members (clients, project managers, admins).
Response 200 OK:
[
{
"id": "del_6831fa2b4c8e1",
"original": "Homepage Design v1",
"url": "https://your-site.com/cp-deliverable/42/homepage-design-v1.pdf",
"linked": false,
"size": 512000,
"type": "application/pdf",
"category": "web-pages",
"uploaded_by": 1,
"uploaded_at": "2025-06-01 10:00:00",
"status": "approved",
"revisions_allowed": 3,
"revisions_used": 1,
"revision_note": "",
"version_count": 2,
"approved_by": 7,
"approved_at": "2025-06-03 14:30:00"
}
]
Field reference:
id(string) — Stable unique ID for this deliverable (persists across version uploads)original(string) — Display name shown in the portalurl(string) — Authenticated serving URL for file deliverables; external URL for linked deliverableslinked(boolean) —trueif this is an external URL rather than an uploaded filesize(integer) — File size in bytes;0for linked deliverablestype(string) — MIME type; empty string for linked deliverablescategory(string) — Category slug from Settings → Deliverables → Categories; empty string if uncategoriseduploaded_by(integer) — WP user ID of the uploaderuploaded_at(string) — Upload datetime of the current versionstatus(string) —pending|in_review|revision_requested|approvedrevisions_allowed(integer) — Total revision rounds permitted (from category or global setting)revisions_used(integer) — Revision rounds consumed so farrevision_note(string) — Client’s note from the most recent revision request; empty string if noneversion_count(integer) — Total versions uploaded (current + archived history)approved_by(integer | null) — WP user ID of the approver, ornullapproved_at(string | null) — Approval datetime, ornull
Interpreting
approved_by: nullon an approved deliverable: When a deliverable is uploaded with the “Require client approval” toggle unchecked, itsstatusis immediatelyapprovedbutapproved_byandapproved_atare bothnull— no approval action was taken. This is distinct from a client-approved deliverable, which has a non-nullapproved_by.
Deliverable statuses:
pending— Uploaded but not yet sent to the client for reviewin_review— Marked ready — client can approve or request revisionsrevision_requested— Client has requested changes; admin/PM should upload a new versionapproved— Client has approved the deliverable
File URLs point to
/cp-deliverable/{portal_id}/{filename}— a logged-in portal member session is required. The fullrevision_historyarray is not included in the REST response; use the portal UI to view the version history accordion.
Get Activity #
GET /cp/v1/portals/{id}/activity
Returns the activity log for the portal in reverse-chronological order.
Query parameters:
limit(integer, default25, max100) — Number of events to returnoffset(integer, default0) — Offset for pagination
Response 200 OK:
{
"total": 47,
"limit": 25,
"offset": 0,
"items": [
{
"id": 123,
"user_id": 1,
"action": "task_completed",
"object_label": "Sign the contract",
"description": "Admin completed <strong>Sign the contract</strong>",
"created_at": "2024-01-18 14:22:00"
},
{
"id": 122,
"user_id": 7,
"action": "message_sent",
"object_label": "",
"description": "Jane Smith sent a message",
"created_at": "2024-01-17 11:05:00"
}
]
}
action values:
portal_created— Portal created via REST APIfile_uploaded— File uploadedfile_linked— External URL added as a file entryfile_deleted— File deletedfile_approval_requested— File uploaded with approval requiredfile_approved— File approvedfile_rejected— File rejecteddeliverable_uploaded— Deliverable uploadeddeliverable_linked— External URL added as a deliverabledeliverable_deleted— Deliverable deleteddeliverable_in_review— Deliverable marked ready for client reviewdeliverable_approved— Deliverable approved by clientdeliverable_revision_requested— Client requested a revisiontask_created— Task addedtask_completed— Task marked completetask_reopened— Task reopenedtask_deleted— Task deletedmessage_sent— Discussion message sentboard_topic_posted— Board topic createdboard_reply_posted— Board reply postedclient_invited— Invitation email sent
Pagination example — fetch events 26–50:
GET /cp/v1/portals/42/activity?limit=25&offset=25
Create Portal #
POST /cp/v1/portals
Creates a new published portal. Optionally assigns a client user and applies a portal template in the same request.
Requires: manage_options (admin)
Request body (JSON):
title(string, required) — Portal namestatus(string, defaultactive) —active|pending|archivedaccent_color(string, default"") — Hex colour, e.g.#ff6600. Empty leaves accent unset.allow_uploads(boolean, defaulttrue) — Whether the client can upload filesclient_user_id(integer, default0) — WP user ID to assign as primary client.0= unassigned.template_id(integer, default0) — Portal template ID to apply.0= no template.
Example:
curl -X POST https://your-site.com/wp-json/cp/v1/portals \
-u "admin:your-app-password" \
-H "Content-Type: application/json" \
-d '{
"title": "Riverside Dental",
"status": "pending",
"accent_color": "#2d6a4f",
"allow_uploads": true,
"template_id": 3
}'
Response 201 Created: Portal object (see field reference above).
{
"id": 58,
"title": "Riverside Dental",
"slug": "riverside-dental",
"url": "https://your-site.com/client-portal/riverside-dental/",
"status": "pending",
"accent_color": "#2d6a4f",
"allow_uploads": true,
"client_user_id": null,
"sub_client_ids": [],
"manager_ids": [],
"template_id": 3,
"template_applied": "2024-02-01 09:15:22",
"created_at": "2024-02-01 09:15:22"
}
Notes:
- If
template_idis provided, the template is applied synchronously before the response is returned — task lists, tools, and the welcome message are populated immediately. - If
client_user_idis provided, the user must already exist in WordPress. To create a new client account, use the invite endpoint after creating the portal. - The portal slug is auto-generated from the title by WordPress.
Send Invitation #
POST /cp/v1/portals/{id}/invite
Sends a client invitation email. The client receives a one-time link to set their password and activate their account. On acceptance, a cp_client role WP user is created and assigned to the portal.
Requires: manage_options (admin)
Request body (JSON):
email(string, required) — Recipient email addressname(string) — Client’s display name, included in the greeting
Example:
curl -X POST https://your-site.com/wp-json/cp/v1/portals/58/invite \
-u "admin:your-app-password" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@riverside-dental.com",
"name": "Jane Smith"
}'
Response 200 OK:
{
"message": "Invitation sent to jane@riverside-dental.com.",
"email": "jane@riverside-dental.com"
}
Error responses:
400 invalid_portal— Portal ID does not exist400 portal_not_published— Portal is a draft — publish it first400 invalid_email— Email address is not valid409 user_exists— A WP account already exists for this email429 rate_limited— More than 20 invitations sent within the last hour500 mail_failed— WordPress could not send the email
Inbound Webhook #
POST /cp/v1/webhook
Accepts action-based payloads from external automation tools (Zapier, Make, n8n, OttoKit, custom scripts). Authenticated via X-CP-API-Key header — no WP user account required.
Supported actions:
create_portal— Create a portal with optional status, accent, client assignment, and templatesend_invite— Send a client invitation email for a specific portalassign_user— Assign an existing WP user to a portal by email addressupdate_portal— Update a portal’s status, accent colour, or upload permission
All actions accept and return JSON. See docs/inbound-webhooks.md for the full payload reference, field descriptions, and example requests for each action.
Example — create a portal and send an invite in one webhook call:
# Step 1: create the portal
curl -X POST https://your-site.com/wp-json/cp/v1/webhook \
-H "X-CP-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"action":"create_portal","title":"Sunset Bakery","status":"pending","template_id":3}'
# Step 2: send the invite
curl -X POST https://your-site.com/wp-json/cp/v1/webhook \
-H "X-CP-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"action":"send_invite","portal_id":58,"email":"owner@sunsetbakery.com","name":"Maria"}'
Verify Connection #
GET /cp/v1/verify
Lightweight endpoint for testing an API key and confirming the plugin is reachable. Used by OttoKit and other integrations during initial setup.
Requires: X-CP-API-Key header
Response 200 OK:
{
"success": true,
"site": "https://your-site.com",
"version": "1.2.0"
}
Automation Examples #
Create a portal, apply a template, and invite the client #
PORTAL=$(curl -s -X POST https://your-site.com/wp-json/cp/v1/portals \
-u "admin:your-app-password" \
-H "Content-Type: application/json" \
-d '{"title":"Acme Corp","status":"pending","template_id":3}')
PORTAL_ID=$(echo $PORTAL | jq '.id')
curl -X POST https://your-site.com/wp-json/cp/v1/portals/$PORTAL_ID/invite \
-u "admin:your-app-password" \
-H "Content-Type: application/json" \
-d '{"email":"client@acme.com","name":"John Doe"}'
Poll for approved deliverables (e.g. from a cron job) #
curl https://your-site.com/wp-json/cp/v1/portals/42/deliverables \
-u "admin:your-app-password" \
| jq '[.[] | select(.status == "approved")]'
Check for revision requests since yesterday (via activity log) #
curl "https://your-site.com/wp-json/cp/v1/portals/42/activity?limit=50" \
-u "admin:your-app-password" \
| jq '[.items[] | select(.action == "deliverable_revision_requested")]'
Error Format #
All errors follow the standard WordPress REST API format:
{
"code": "rest_forbidden",
"message": "You do not have access to this portal.",
"data": { "status": 403 }
}
Not Yet Available via REST #
The following operations remain AJAX-only in the current version and will be added in a future release:
- Create / update / delete tasks and task lists
- Upload or delete files
- Upload, approve, or request revisions on deliverables
- Post discussion messages
- Post board topics and replies
- Assign project managers to portals
- Manage portal template definitions
PortalTemplates::apply_to_portal() is already callable directly from PHP for headless or plugin-to-plugin use cases without waiting for the REST endpoint.
