Start here
Quickstart
The fastest contract check is an authenticated GET /voices. It does not create data and is available to service credentials with practices:read.
Approved organization owners and admins can create, rotate, and revoke credentials in the Developer Portal. Organization access remains controlled.
1. Store the credential
Keep the service credential on your server. Never expose it in browser or mobile client code.
export PSYMIND_API_TOKEN="your_service_credential"2. Make your first call
curl "https://psymind.app/api/v1/voices?language=en" \
-H "Authorization: Bearer $PSYMIND_API_TOKEN"
const response = await fetch(
'https://psymind.app/api/v1/voices?language=en',
{ headers: { Authorization: `Bearer ${process.env.PSYMIND_API_TOKEN}` } }
);
const body = await response.json();
if (!response.ok) throw new Error(`${body.error?.code}: ${body.error?.message}`);
import os
import requests
response = requests.get(
"https://psymind.app/api/v1/voices",
params={"language": "en"},
headers={"Authorization": f"Bearer {os.environ['PSYMIND_API_TOKEN']}"},
timeout=10,
)
response.raise_for_status()
body = response.json()
3. Keep the request ID
Error responses include error.request_id. Preserve it in your server logs without recording sensitive client content.
Authentication
Send a bearer token with every protected request:
Authorization: Bearer <token>The contract supports two actor types:
- Human actor: a Supabase user access token. Access is filtered by membership and role.
- Service actor: an opaque PsyMind service credential bound to one organization and an explicit scope set.
When a human actor belongs to multiple organizations, send X-PsyMind-Organization-Id. Service credentials already carry their organization boundary and do not use this header to switch tenants.
Service scopes
Scopes are operation-specific and declared in OpenAPI. Current B2B scope families are:
| Resource | Read | Write |
|---|---|---|
| Organizations | organizations:read | organizations:write |
| Practitioners | practitioners:read | practitioners:write |
| Clients | clients:read | clients:write |
| Client context | context:read | context:write |
| Practices | practices:read | practices:write |
| Assignments | assignments:read | assignments:write |
| Outcomes | outcomes:read | outcomes:write |
Model Context Protocol
PsyMind MCP server
The controlled-access MCP endpoint is https://psymind.app/api/mcp. It is a thin transport over API v1: every business operation uses the same scopes, tenant boundary, idempotency, rate limits, and error contracts as the HTTP API.
Use an organization-bound PsyMind service credential. Clients that can send a static bearer header can connect. OAuth discovery, Dynamic Client Registration, and interactive consent are not available in the current API credential flow.
{
"url": "https://psymind.app/api/mcp",
"headers": {
"Authorization": "Bearer <resolved-by-your-secret-manager>"
}
}Tool catalog
| Tool | API v1 operation | Scope |
|---|---|---|
create_practice | POST /practices | practices:write |
get_practice | GET /practices/{practiceId} | practices:read |
list_practices | GET /practices | practices:read |
get_client | GET /clients/{clientId} | clients:read |
assign_practice | POST /practices/{practiceId}/assign | assignments:write |
get_practice_audio | GET /practices/{practiceId}/audio | practices:read |
Mutating tools require a stable idempotency key. Practice listing uses the API's opaque cursor and a limit from 1 to 50. Tool results include the API request ID for support correlation.
Deliberate gaps
get_client_history and get_usage have no API v1 operation. get_outcomes is withheld because its current API operation is not paginated. Audio generation is requested through create_practice(generate_audio=true), then retrieved with get_practice_audio.
Readiness: https://psymind.app/api/mcp-health. Full schemas, errors, limits, and security boundaries are maintained with the server source.
Clients
Client records are tenant-scoped integration objects. Use your stable identifier as external_id; PsyMind returns its own resource ID for subsequent requests.
| Method | Path | Purpose |
|---|---|---|
| POST | /clients | Create a client |
| GET | /clients | List clients |
| GET | /clients/{clientId} | Retrieve a client |
| PATCH | /clients/{clientId} | Update a client |
| GET | /clients/{clientId}/context | Read personalization context |
| POST | /clients/{clientId}/context | Update personalization context |
curl -X POST "https://psymind.app/api/v1/clients" \
-H "Authorization: Bearer $PSYMIND_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"external_id": "customer_1042",
"preferred_language": "en",
"timezone": "Europe/London",
"consent_status": "granted"
}'Practices
Create a personalized practice for an existing client. The create operation requires client_id and goal and returns an asynchronous resource; retrieve it until it reaches a terminal state.
| Method | Path | Purpose |
|---|---|---|
| POST | /practices | Create a practice |
| GET | /practices | List practices |
| GET | /practices/{practiceId} | Retrieve status and metadata |
| POST | /practices/{practiceId}/regenerate | Request regeneration |
| GET | /practices/{practiceId}/audio | Retrieve audio metadata |
curl -X POST "https://psymind.app/api/v1/practices" \
-H "Authorization: Bearer $PSYMIND_API_TOKEN" \
-H "Idempotency-Key: practice-customer-1042-001" \
-H "Content-Type: application/json" \
-d '{
"client_id": "00000000-0000-4000-8000-000000000000",
"goal": "Prepare for a focused work session",
"language": "en",
"duration_seconds": 600,
"generate_audio": true
}'The API creates personalized practices and self-suggestion content. It is not a diagnostic, treatment, cure, or replacement for professional care.
Assignments
An assignment connects a ready practice to a client. Lifecycle writes support idempotency, so retry the same logical transition with the same key.
| Method | Path | Purpose |
|---|---|---|
| POST | /practices/{practiceId}/assign | Create an assignment |
| GET | /assignments | List assignments |
| GET | /assignments/{assignmentId} | Retrieve an assignment |
| POST | /assignments/{assignmentId}/started | Record started |
| POST | /assignments/{assignmentId}/completed | Record completed |
curl -X POST "https://psymind.app/api/v1/practices/$PRACTICE_ID/assign" \
-H "Authorization: Bearer $PSYMIND_API_TOKEN" \
-H "Idempotency-Key: assignment-customer-1042-001" \
-H "Content-Type: application/json" \
-d '{ "client_id": "00000000-0000-4000-8000-000000000000" }'Outcomes
Outcomes are structured measurements recorded against an assignment. Every write requires a metric name, type, value, and phase.
| Method | Path | Purpose |
|---|---|---|
| POST | /assignments/{assignmentId}/outcomes | Record a measurement |
| GET | /assignments/{assignmentId}/outcomes | List measurements |
curl -X POST "https://psymind.app/api/v1/assignments/$ASSIGNMENT_ID/outcomes" \
-H "Authorization: Bearer $PSYMIND_API_TOKEN" \
-H "Idempotency-Key: outcome-assignment-001-post" \
-H "Content-Type: application/json" \
-d '{
"metric_name": "focus_readiness",
"metric_type": "rating",
"metric_value": 8,
"phase": "post",
"source": "partner_app"
}'Webhooks
API v1 delivers one minimal notification per canonical B2B transition through a transactional outbox. Manage endpoints with /webhook-endpoints, inspect history with /webhook-deliveries, and use webhooks:read/webhooks:write service scopes. The signing secret is revealed only on create or rotation; deleting an endpoint revokes its encrypted secret.
For the complete event catalog, signed payload contract, retry behavior, security requirements, and curl/TypeScript/Python examples, see the dedicated Webhooks guide.
Secret rotation
Send PATCH /v1/webhook-endpoints/{webhookEndpointId} with {"regenerate_secret":true}. Rotation is an atomic tenant-scoped compare-and-swap: one concurrent request receives the new one-time signing_secret. A stale concurrent request receives HTTP 409 with error.code=webhook_secret_rotation_conflict and no plaintext; retry the complete PATCH.
Canonical events
practice.created,practice.ready,practice.failedaudio.ready,audio.failedpractice.assigned,practice.started,practice.completedoutcome.submitted
Each payload contains only event id/type/time/version and data.object with resource id/type/status. It excludes names, prompts, notes, free text, client identifiers, outcome values, and sensitive context.
Signature headers
PsyMind-Event-Id: <event-id>
PsyMind-Timestamp: <unix-seconds>
PsyMind-Signature: v1=<64-lowercase-hex-HMAC>
Idempotency-Key: <same-event-id>Verify HMAC-SHA256 over {PsyMind-Timestamp}.{exact-raw-body} with the full whsec_ secret before parsing JSON. Use constant-time comparison, reject timestamps outside ±300 seconds, and require the parsed payload id to equal PsyMind-Event-Id. Persist that event id atomically before side effects; if it already exists, return 2xx without repeating work.
import { verifyWebhook } from "@psymind/sdk";
const event = verifyWebhook(rawBody, request.headers, process.env.PSYMIND_WEBHOOK_SECRET);
// INSERT event.id with a UNIQUE constraint before processing.Delivery behavior
- Every 2xx response is success; redirects, all non-2xx responses, network errors, and the 10-second timeout are failures.
- Up to seven attempts: immediate, then 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours, and 24 hours.
- Retries and manual replay reuse the same event id and payload. Manual replay is available only for a terminally failed delivery.
webhook.testuses the same signed outbox/delivery/history pipeline and targets only the selected endpoint.- Endpoints must use public HTTPS port 443; redirects and private, loopback, link-local, reserved, mixed-DNS, IPv4/IPv6, and DNS-rebinding destinations are rejected.
Errors
Non-success responses use one envelope. Branch on error.code; use the human-readable message for diagnostics, not control flow.
{
"error": {
"code": "invalid_request",
"message": "The request could not be processed.",
"request_id": "req-…",
"details": {}
}
}Expect contract-declared 4xx and 5xx responses per operation. A 409 can indicate an idempotency-key conflict or domain conflict; 429 indicates throttling.
Rate limits
Operations declare a rate-limit class such as read, search, or write. Numeric limits are deployment configuration and are not documented as fixed API promises.
On 429, read X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After, then retry after the indicated delay with bounded jitter.
Idempotency
Supported write operations accept Idempotency-Key. The key is organization-scoped and may contain 1–200 characters matching letters, digits, dot, underscore, colon, or hyphen.
- Same key + same payload: returns the same logical resource for 24 hours.
- Same key + different payload: returns HTTP 409.
- Use a stable key per business action, not a random key per retry.
The generated API Reference identifies operations where the header is declared.
Pagination
List endpoints use limit and an opaque signed cursor. The default limit is 20 and the allowed range is 1–50.
- Follow the returned cursor; do not decode or construct it.
- A cursor is bound to route, sort, order, normalized filters, and relevant authorization context.
- It is not a database snapshot. On mutable listings, deduplicate resource IDs across pages.
curl "https://psymind.app/api/v1/clients?limit=20&cursor=$NEXT_CURSOR" \
-H "Authorization: Bearer $PSYMIND_API_TOKEN"Full contract
Open the generated API Reference for operation-level parameters, required fields, scopes, rate classes, and response codes. Its source version and SHA-256 prefix are displayed in the reference header.