Skip to main content

Admin Companies API Endpoints

The Admin Companies API provides management endpoints for company records. Companies represent organizations associated with listed items. The API supports full CRUD operations with Zod-based validation, domain/slug uniqueness enforcement, and optional CRM synchronization on updates.

Route Summary

MethodPathAuthDescription
GET/api/admin/companiesAdminList companies (paginated, searchable)
POST/api/admin/companiesAdminCreate a new company
GET/api/admin/companies/{id}AdminGet a single company by UUID
PUT/api/admin/companies/{id}AdminUpdate a company
DELETE/api/admin/companies/{id}AdminPermanently delete a company

Authentication

All company endpoints verify the session has admin privileges:

const session = await auth();
if (!session?.user?.isAdmin) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

Endpoints

GET /api/admin/companies

Returns a paginated list of companies with search and status filtering. Also returns global counts of active and inactive companies regardless of filters applied.

Query Parameters:

ParameterTypeDefaultDescription
pageinteger1Page number (must be >= 1)
limitinteger10Items per page (1--100)
qstring--Search by name or domain (case-insensitive)
statusstring--Filter: "active" or "inactive"

Response (200):

{
"success": true,
"data": {
"companies": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"website": "https://acme.com",
"domain": "acme.com",
"slug": "acme-corporation",
"status": "active",
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-20T14:45:00.000Z"
}
]
},
"meta": {
"page": 1,
"totalPages": 5,
"total": 47,
"limit": 10,
"activeCount": 40,
"inactiveCount": 7
}
}

The meta.activeCount and meta.inactiveCount values reflect global totals and are not affected by the q or status filters. This allows the UI to show tab counts alongside filtered results.

POST /api/admin/companies

Creates a new company record. Request data is validated with Zod schema (createCompanySchema). Domain and slug values are normalized to lowercase. Uniqueness is checked for both domain and slug before insertion.

Request Body:

{
"name": "Acme Corporation",
"website": "https://acme.com",
"domain": "acme.com",
"slug": "acme-corporation",
"status": "active"
}
FieldTypeRequiredDescription
namestringYesCompany name (1--255 characters)
websitestring (URI)NoFull website URL
domainstringNoNormalized domain (max 255 characters)
slugstringNoURL-friendly identifier (^[a-z0-9-]+$, max 255)
statusstringNo"active" or "inactive" (default: "active")

Validation: Uses Zod schema validation. On failure, returns detailed field-level errors:

{
"error": "Validation error",
"details": [
{ "field": "name", "message": "Company name is required" }
]
}

Response (201):

{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"website": "https://acme.com",
"domain": "acme.com",
"slug": "acme-corporation",
"status": "active",
"createdAt": "2024-01-20T16:45:00.000Z",
"updatedAt": "2024-01-20T16:45:00.000Z"
}
}

GET /api/admin/companies/{id}

Retrieves a single company by its UUID.

Path Parameters:

ParameterTypeDescription
idstring (UUID)Company unique identifier

Response (200):

{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation",
"website": "https://acme.com",
"domain": "acme.com",
"slug": "acme-corporation",
"status": "active",
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-20T14:45:00.000Z"
}
}

PUT /api/admin/companies/{id}

Updates an existing company. Supports partial updates -- only provided fields are changed. Validated with updateCompanySchema. Domain and slug uniqueness is re-verified when those fields change. After a successful update, the company data is optionally synced to a CRM system.

Path Parameters:

ParameterTypeDescription
idstring (UUID)Company unique identifier

Request Body:

{
"name": "Acme Corporation Updated",
"website": "https://acme.com",
"status": "active"
}

All fields are optional. Only supplied fields will be updated.

CRM Synchronization:

When TWENTY_CRM_ENABLED is not set to "false", the updated company is automatically synced to the Twenty CRM system. This sync is non-blocking -- if it fails, the API still returns success for the database update:

const syncService = createTwentyCrmSyncServiceFromEnv();
const companyPayload = mapCompanyToTwentyCompany(company);
await syncService.upsertCompany(companyPayload);

Response (200):

{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Acme Corporation Updated",
"status": "active",
"updatedAt": "2024-01-20T16:30:00.000Z"
}
}

DELETE /api/admin/companies/{id}

Permanently deletes a company. This is a hard delete -- the record is removed from the database. Associated item-company links are removed via CASCADE constraints.

Path Parameters:

ParameterTypeDescription
idstring (UUID)Company unique identifier

Response (200):

{
"success": true,
"message": "Company deleted successfully"
}
caution

Company deletion is permanent and cannot be undone. All item associations for the deleted company will be removed through database CASCADE rules.

Validation Rules

Company data is validated using Zod schemas defined in lib/validations/company.ts:

FieldRule
nameRequired, 1--255 characters
websiteOptional, must be valid URI format
domainOptional, max 255 characters, normalized to lowercase
slugOptional, max 255 characters, lowercase alphanumeric and hyphens only
statusOptional, must be "active" or "inactive"

Error Codes

StatusErrorCause
400Validation errorZod schema validation failure (includes field details)
400Invalid page parameterPage is not a positive integer
400Invalid limit parameterLimit outside 1--100 range
401UnauthorizedMissing or non-admin session
404Company not foundNo company with the given UUID
409Company with domain already existsDomain uniqueness violation
409Company with slug already existsSlug uniqueness violation
500Failed to create/update/delete companyServer or database error