User-creation endpoints
Every endpoint that lets a logged-in user (or an API caller) create another user account in Tasklife. Covers the web admin form, the self-service agent form, the public REST API, and the closely-related credential/manager endpoints you will need immediately after creating a user.
Not covered: marketing signup (
signup.php, free-tier only), OAuth registration of MCP clients, and the admin CLI.
Which endpoint should I use?
| Use case | Endpoint | Auth | User types | Sets manager? |
|---|---|---|---|---|
| Self-service: any user adds an agent they will manage | POST /main/api/profile_create_agent.php |
Session | agent only | Yes — caller becomes the manager |
| Org admin onboarding a human teammate or a bot via the UI | POST /admin/admin_user_add.php |
Session + is_org_admin |
human or agent | Yes — manager picker in the form |
| External automation provisioning an agent under an API key | POST /v1/agents |
API key (role=org_admin) |
agent only | No — set with reports_to_org_user_id via the web admin today |
| Programmatic creation of a human user | Not yet exposed | — | — | — |
POST /v1/agents) but does not expose a POST /v1/users endpoint for human users. The GET and PATCH /v1/users/{id} endpoints exist, but creation is admin-UI only today. The reports_to_org_user_id column also has no public-API PATCH endpoint; manager assignment after creation still requires the admin UI or a direct DB write by an operator.
POST
/main/api/profile_create_agent.php
Self-service endpoint any logged-in user can call to create an agent in the org they are currently viewing. The calling user becomes the new agent's manager (org_users.reports_to_org_user_id). Used by the "Create Agent" button on /main/profile.php.
Request
JSON body, session cookie required.
POST /main/api/profile_create_agent.php HTTP/1.1
Host: tasklife.com
Content-Type: application/json
Cookie: PHPSESSID=...
{
"name": "Cody the Coder",
"handle": "cody",
"email": "[email protected]"
}
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Display name. Max 255 chars. |
handle | string | yes | Unique across all orgs. Pattern: /^[a-zA-Z0-9_]+$/, max 50 chars. |
email | string | optional | If blank, a synthetic email (agent+<org>_<token>@agents.stracker.local) is generated. If provided, must pass FILTER_VALIDATE_EMAIL. |
Response (201-style success)
{
"success": true,
"data": {
"user": {
"id": 142,
"org_user_id": 211,
"name": "Cody the Coder",
"handle": "cody",
"email": "[email protected]",
"user_type": "agent",
"org_id": 12,
"reports_to_org_user_id": 79,
"status": "active"
}
}
}
Errors
| HTTP | code | When |
|---|---|---|
| 422 | VALIDATION | Missing/invalid name, handle, or email. error.fields lists each problem. |
| 409 | HANDLE_TAKEN | Handle already exists in org_users or users. |
| 403 | NO_MEMBERSHIP | Caller has no org_users row in their current org. |
| 422 | NO_ORG_CONTEXT | Session has no org_id. |
| 401 | UNAUTHORIZED | Not logged in. |
curl example
curl -sS -X POST https://tasklife.com/main/api/profile_create_agent.php \
-H 'Content-Type: application/json' \
-b 'PHPSESSID=YOUR_SESSION' \
-d '{"name":"Cody the Coder","handle":"cody","email":"[email protected]"}'
POST
/admin/admin_user_add.php
Admin web form. Accepts either application/x-www-form-urlencoded body (default browser submit) or a JSON-style action under ui_access_action. The form supports both human and agent user types, lets the admin pick a manager via a typeahead, and can optionally email the new login credentials back to the admin.
Request (form-encoded)
POST /admin/admin_user_add.php HTTP/1.1
Host: tasklife.com
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=...
name=Cody+the+Coder
&user_type=agent
&email=
&handle=cody
&status=active
&reports_to_org_user_id=79
&new_password=
&address=
&city=
&state=
&postal_code=
&country=
| Field | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Display name. |
user_type | enum | yes | human or agent. |
email | string | required for human, optional for agent | Synthetic email generated for agents when blank. |
handle | string | optional | Pattern /^[a-zA-Z0-9_]+$/. |
status | enum | default active | active, inactive, etc. |
reports_to_org_user_id | int | optional | Must reference an active org_users.id in the same org. Setting this is how you assign a manager. |
new_password | string | optional | If set, must be ≥ 4 chars. Stored as bcrypt hash. |
address / city / state / postal_code / country | string | optional | Persisted on org_users. |
ui_access_action=email_payload | string | optional | If set, posts an email with the credentials payload to ui_access_email. |
Response
Standard PHP form response: redirects with a success_message or error_message query string. The credentials payload (if a password was supplied) is also stashed in the admin session for one-time display/copy on the form.
Notes
- This is the only path today that creates human users via the UI.
- If the email already exists in
usersthe existing user is updated (name + user_type) and the existingusers.idis reused — only theorg_usersrow is inserted. - If
org_users.user_email = ?already exists in the same org, the request fails with "already associated with this organization".
Source: admin/admin_user_add.php lines 99-247.
POST
/v1/agents
Public REST API endpoint for creating agent users. Requires an API key whose org_users.role = 1 (org admin). The new agent is created in the org the API key belongs to. This is the entry point an external CI/automation pipeline uses for bot onboarding.
Request
POST /v1/agents HTTP/1.1
Host: api.tasklife.com
Authorization: Bearer tsk_xxx...your_api_key
Content-Type: application/json
{
"display_name": "Cody the Coder",
"handle": "cody",
"role": 2,
"description": "On-call CI fix-it bot"
}
| Field | Type | Required | Notes |
|---|---|---|---|
display_name (or name) | string | yes | Goes into users.name. |
handle | string | optional | Pattern: /^[a-z0-9][a-z0-9._-]{1,63}$/i. If set, must be unique inside the API key's org. |
role | int | optional, default 2 | 1 = org admin, 2 = member, 3 = read-only. Invalid values fall back to 2. |
description | string | optional | Currently not persisted; included for forward-compat. |
Response (201)
{
"success": true,
"data": {
"agent": {
"id": 142,
"org_user_id": 211,
"display_name": "Cody the Coder",
"handle": "cody",
"role": 2,
"org_id": 12,
"user_type": "agent",
"created_at": "2026-08-23T18:42:11+00:00"
}
}
}
Errors
| HTTP | code / message | When |
|---|---|---|
| 400 | display_name is required | Empty name. |
| 400 | Invalid handle format | Handle fails regex. |
| 409 | Handle already in use in this org | Handle collision. |
| 403 | Admin access required | API key's org role ≠ 1. |
| 401 | missing/invalid Authorization: Bearer | API key auth failed upstream. |
curl example
curl -sS -X POST https://api.tasklife.com/v1/agents \
-H "Authorization: Bearer $TASKLIFE_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"display_name":"Cody the Coder","handle":"cody","role":2}'
What POST /v1/agents does NOT do
- It does not set
reports_to_org_user_id. A freshly-created agent is "Top" (reports to no one). Use the admin UI today to attach a manager. - It does not create a credential. See the credential endpoints below.
- It only creates agents; for humans, use the admin form.
Source: external-api/v1/index.php lines 1502-1512 and main/api/lib/AgentUsersService.php::createAgent().
After creation: what's next?
For an agent you will typically follow up with one or more of these calls:
| Endpoint | Purpose |
|---|---|
POST /v1/agents/{id}/credentials |
Mint a long-lived API key for the agent. Only the plaintext key returned at creation time is ever shown again — Tasklife stores a hash. Capture it client-side immediately. |
POST /v1/agents/{id}/credentials/{key_id}/rotate |
Rotate an existing credential; old plaintext is invalidated. |
POST /v1/agents/{id}/token/mint |
Mint a one-time Agent UI login token (1-120 min TTL). Agent follows the returned target_url, completes OAuth device-grant, and lands authenticated. |
GET /v1/agents/{id}/invitation |
Returns an MCP invitation bundle (handle + setup URL, no plaintext key) the admin can hand to the agent's operator. |
PATCH /v1/users/{org_user_id} |
Update handle and webhook config for an existing user. Not yet wired for reports_to_org_user_id; that still requires the admin UI. |
See /docs/api/agents/ for full per-endpoint reference and /docs/mcp/ for the MCP server side of the OAuth device-grant flow.
Data model in 60 seconds
Tasklife stores a person as two rows:
users— global identity:id,email,name,user_type(human | agent),password_hash,handle.org_users— per-org membership:id,org_id,user_id(oruser_emailwhen the user row doesn't exist yet),role,status,handle,reports_to_org_user_id, address fields, webhook config.
Every API payload you receive uses both ids:
id is the users.id (global), org_user_id is the org_users.id (per-org). Manager relationships are org_users.reports_to_org_user_id pointing at another org_users.id in the same org — an org can have multiple managers, and any user (human or agent) can be a manager. The column is nullable; a NULL value means "Top" (reports to no one).
Page version: 2026-08-23. Authoritative source of truth for the public REST API lives in external-api/v1/index.php; for the web forms, in admin/admin_user_add.php and main/api/profile_create_agent.php.