Supercommerce API Docs
Admin API

Vendor Module — Admin

HTTP surface for the platform-admin side of vendor onboarding: list / inspect vendor applications, approve them (provisioning a Better-Auth organization + vendor profile) or…

HTTP surface for the platform-admin side of vendor onboarding: list / inspect vendor applications, approve them (provisioning a Better-Auth organization + vendor profile) or reject them with a reason; and list / inspect existing approved vendors via the vendor directory. The vendor-self application submit / "my applications" endpoints live in the same module but are out of scope here (see vendor/vendor.md).

Source: api-modules/vendor/src/controllers/vendor-admin.controller.ts, api-modules/vendor/src/controllers/vendor-management.controller.ts. The vendor-self vendor-application.controller.ts belongs to the vendor folder, not this one.

Approving an application provisions a Better-Auth organization (the vendor's tenant within the platform) and creates the vendor profile row. The applicant becomes the organization's first owner member.


Conventions

Authentication

All endpoints require a Better-Auth admin session and a role granting the matching vendor:* permission.

Endpoint groupPermission
GET /admin/vendor/applications, GET /admin/vendor/applications/:idvendor: view
POST /admin/vendor/applications/:id/approve, POST /admin/vendor/applications/:id/rejectvendor: approve
GET /admin/vendors, GET /admin/vendors/:idvendor: view

Response envelope

Successful responses are wrapped by ResponseInterceptor:

{
  "data": <payload>,
  "message": "Success",
  "statusCode": 200,
  "metadata": { /* optional, e.g. pagination */ }
}

Error envelope

statusCodeerrorCode examples
400BAD_REQUEST, VALIDATION_ERROR
401UNAUTHORIZED
403FORBIDDEN
404NOT_FOUND
409CONFLICT, UNIQUE_VIOLATION (slug taken at approval time)
500INTERNAL_SERVER_ERROR, DATABASE_ERROR

Application lifecycle

vendor_application.status values:

StatusSet byReversible
pendingPOST /vendor/applications (vendor-self)yes — admin acts on it
approvedPOST /admin/vendor/applications/:id/approveno
rejectedPOST /admin/vendor/applications/:id/rejectno — applicant must submit a new application

A user may have at most one application in pending (enforced on the vendor-self submit side).


Domain types

ApplicationResponse

type ApplicationStatus = "pending" | "approved" | "rejected";

type ApplicationResponse = {
  id: string;
  userId: string;                  // applicant's user id
  businessName: string;
  slug: string;                    // lowercase alnum + hyphens; unique on approval
  businessEmail: string;
  businessPhone: string;
  businessDescription: string;
  status: ApplicationStatus;
  rejectionReason: string | null;
  reviewedBy: string | null;       // admin user id
  reviewedAt: string | null;       // ISO
  createdAt: string;
  updatedAt: string;
};

Vendor profile (admin reads)

The admin vendor list returns the Better-Auth organization joined with the vendor profile plus a team-member count. Exact shape lives in VendorProfileService; at minimum:

type VendorProfileSummary = {
  id: string;                      // == organization id (the vendor id used everywhere else)
  businessName: string;
  slug: string;
  businessEmail: string;
  businessPhone: string;
  businessDescription: string;
  memberCount: number;
  createdAt: string;
  updatedAt: string;
  members?: Array<{                // only on the detail endpoint
    userId: string;
    email: string;
    name: string;
    role: string;                  // "owner" | "admin" | "member"
    joinedAt: string;
  }>;
};

Applications

Base path: /admin/vendor/applications.

GET /admin/vendor/applications — List applications

Required permission: vendor: view. Standard QueryDto (page / limit / search / filters[]).

Response 200 — paginated envelope of ApplicationResponse[].


GET /admin/vendor/applications/:id — Application detail

Required permission: vendor: view. Returns ApplicationResponse plus the applicant's user details (name, email, image) — joined in the service.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown id

POST /admin/vendor/applications/:id/approve — Approve

Required permission: vendor: approve. Allowed only from pending.

Side effects

  • Validates slug uniqueness against the organization slug column. On collision → 409 UNIQUE_VIOLATION.
  • Creates a new Better-Auth organization with slug = application.slug and name = businessName.
  • Adds the applicant as the organization's owner member.
  • Creates the vendor_profile row pointing at the organization.
  • Stamps status="approved", reviewedBy = current admin user id, reviewedAt.
  • Emits vendor.application.approved (consumed by notifications — sends a welcome email).

The applicant's existing session does not automatically gain access to the new organization — they need to renew the session so Better-Auth surfaces the new activeOrganizationId.

Response 200 — updated ApplicationResponse.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown application id
409CONFLICTApplication is not pending
409UNIQUE_VIOLATIONslug already taken by another organization since submission

POST /admin/vendor/applications/:id/reject — Reject

Required permission: vendor: approve. Allowed only from pending.

Body

{ "reason": "Required documents not provided" }
FieldTypeConstraints
reasonstring1..2000

Side effects

  • Stamps status="rejected", rejectionReason, reviewedBy, reviewedAt.
  • Emits vendor.application.rejected (consumed by notifications — sends an email with the reason).

Response 200 — updated ApplicationResponse.

Errors

StatusCodeWhen
400VALIDATION_ERROREmpty / oversized reason
404NOT_FOUNDUnknown application id
409CONFLICTApplication is not pending

Vendor directory

Base path: /admin/vendors.

GET /admin/vendors — List approved vendors

Required permission: vendor: view. Standard QueryDto. Returns approved vendors with profile + member count.

Response 200 — paginated envelope of VendorProfileSummary[] (without the members[] field).


GET /admin/vendors/:id — Vendor detail

Required permission: vendor: view. Returns the full VendorProfileSummary with members[] populated.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown vendor id

Domain events

Emitted via EventEmitter2. Listeners include the notifications module (onboarding / rejection emails).

EventFired when
vendor.application.approvedPOST /admin/vendor/applications/:id/approve
vendor.application.rejectedPOST /admin/vendor/applications/:id/reject

  • admin-rbac — gates every endpoint via vendor:view / vendor:approve. See admin-rbac.md.
  • auth — owns the Better-Auth user + organization + member tables. Approval creates an organization row.
  • settings — vendor self-service settings (shipping, tax, etc.) are scoped to the organization id created here. The platform-admin override endpoints in settings.md / shipping.md / tax.md use the same vendor id.
  • notifications — consumes the vendor.application.* events to email the applicant.

On this page