External management API
These routes let an external application provision companies and add and
update its company users. They live under /api/v1/external/manage/** and
are separate from the read-only, token-scoped
directory endpoints.
Scope. Category administration and user removal are deliberately not exposed externally — they are performed through the support console. Company provisioning (idempotent create + app link + first user) and user create and update are callable with an app key.
Authentication
- The calling application is identified only by the
X-App-Public-Keyheader. The key is resolved to an active application; an unknown, malformed, or inactive key is rejected with400/403. - There is no per-user bearer token on these routes. Treat the public key as a privileged secret here and serve these routes over TLS only.
- All target UUIDs travel in the JSON body, so one app key can manage multiple companies.
All operations are recorded in the audit log.
Response envelope
Every management route returns the same three keys, on success and failure alike, so a client reads the outcome from the payload rather than the HTTP layer:
{ "status": 201, "message": "Created", "body": { "...": "..." } }
{ "status": 404, "message": "Company not found", "body": {} }
body is always present — an empty object when there is nothing to return.
Bean-validation failures name the offending fields in message (for example
"slug must not be blank; uuid must not be null"). The read routes under
/api/v1/external/** are not enveloped and return bare payloads.
Provision a company (idempotent)
POST /api/v1/external/manage/companies
Creates a company, subscribes it to an application, and creates-and-links its first user in one call. The call is idempotent: repeating it with the same body never fails — whatever already exists is left in place and only the missing pieces are created.
Request (all six fields required; snake_case is canonical, and the camelCase
aliases companyName, userName, userEmail, appId are also accepted):
{
"uuid": "a3328ccf-0b64-4b2a-b735-886c330d57d5",
"company_name": "Acme Ltd",
"slug": "acme-ltd",
"user_name": "John Doe",
"user_email": "j.doe@acme.example",
"app_id": "hrm"
}
Processing order:
- Validate the application public key.
- Resolve
app_idto an application; an unknown value fails with404 Application not foundbefore anything is written. - Match the company by
uuidonly:- Not found → create it with the supplied
uuid,company_nameandslug, immediatelyACTIVE(no approval step). Aslugalready owned by a different company fails with409 Company slug already exists. - Found → the existing company is left completely untouched (name, slug and status are not modified).
- Not found → create it with the supplied
- Ensure the company is subscribed to the application. An existing
ACTIVElink (including category-scoped access configuration) is left as-is. - Look up
user_emailcase-insensitively. A missing user is created email-verified with a generated password and sent the password-setup email; an existing user is reused without changes. - Ensure the
(user, company)membership. A missing membership is created with company roleADMIN(and triggers the membership-added email); an existing membership keeps its current role and status.
Responses — 201 Created when the company was created on this call,
200 Updated when it already existed; body.company is the company and
body.user the company-user membership:
{ "status": 201, "message": "Created", "body": { "company": { "id": "...", "name": "Acme Ltd", "slug": "acme-ltd", "status": "ACTIVE" }, "user": { "userId": "...", "email": "j.doe@acme.example", "role": "ADMIN" } } }
{ "status": 200, "message": "Updated", "body": { "company": { "...": "..." }, "user": { "...": "..." } } }
{ "status": 404, "message": "Application not found", "body": {} }
Audit actions recorded for the steps that actually ran: CREATE_COMPANY,
SET_COMPANY_STATUS, SUBSCRIBE_COMPANY_APPLICATION, CREATE_USER,
ATTACH_USER_COMPANY.
Create or link a company user
POST /api/v1/external/manage/users
Body fields: companyId (required), email (required), name (required),
password?, systemRole?, role?, emailVerified?.
Minimal request:
{
"companyId": "a3328ccf-0b64-4b2a-b735-886c330d57d5",
"email": "j.doe@acme.example",
"name": "John Doe"
}
Processing order:
- Validate the application public key.
companyIdmust identify an existing company; otherwise404 Company not found.- Look up
emailcase-insensitively. - An existing user is linked to the company without changing its profile, credentials, or verification state.
- A missing user is created, sent its password-setup email, and then linked.
- After attachment, the user is emailed that they were added to the named company.
Returns 201 with the company-user membership in body. Notes:
- The company
roleisOWNER/ADMIN/MEMBER, defaultMEMBER. emailVerifiedis ignored — users created here are always email-verified. The field is accepted only so existing callers don't break.- A user can belong to multiple companies; the
(user, company)membership is unique. Repeating the request keeps one membership, reactivates it, applies the requested role, and resends the attachment notification.
Update a company user
PUT /api/v1/external/manage/users
Body fields: companyId (required), userId (required), name?,
phoneNumber?, avatarUrl?, locale?, timezone?, status?, role?.
{
"companyId": "a3328ccf-0b64-4b2a-b735-886c330d57d5",
"userId": "5f5e1a3c-1111-2222-3333-444455556666",
"name": "John D.",
"role": "ADMIN"
}
Updates the profile fields provided; pass role to change the company role.
Returns 200 with the membership in body.
Removed routes
These were withdrawn from the external API; use the support console instead.
Calling them returns 404:
| Method | Path |
|---|---|
| PUT / DELETE | /api/v1/external/manage/companies |
| POST / PUT / DELETE | /api/v1/external/manage/categories |
| DELETE | /api/v1/external/manage/users |
| PUT | /api/v1/external/manage/users/categories |
Detachment (suspending a membership) is performed by your Authfy administrator; it suspends rather than deletes, so a membership can be reactivated later, and the user is notified by email.