Skip to main content

Companies Block

Companies Block

The Companies Block provides a complete Teams & Organizations API for building B2B applications. Manage organizations, team memberships, roles, permissions, and multi-tenancy - all through a simple REST API.

Features

  • Organization Management - Create and manage unlimited organizations
  • Team Memberships - Add members with flexible team structures
  • Role-Based Permissions - Define custom roles with granular access control
  • Invitations & Onboarding - Email invitations with customizable flows
  • Multi-Tenancy - Complete data isolation between organizations
  • Billing & Subscriptions - Per-organization billing integration
  • Domain Verification - Verify and claim company domains
  • SSO Integration - SAML and OIDC per organization

API Endpoint

ServiceURL
Companieshttps://companies.api.us.23blocks.com

Environment Routing: Use the same URL for all environments. Your API key determines the environment:

  • pk_test_* / sk_test_* → Routes to Staging
  • pk_live_* / sk_live_* → Routes to Production

Quick Start

npm install @23blocks/sdk
import { create23BlocksClient } from '@23blocks/sdk';

const client = create23BlocksClient({
urls: { companies: 'https://companies.api.us.23blocks.com' },
apiKey: 'your-api-key', // Use pk_test_* for staging, pk_live_* for production
});

// Create an organization
const org = await client.companies.create({
name: 'Acme Corporation',
slug: 'acme',
ownerId: 'user-123'
});

// Invite team members
await client.companies.invite({
orgId: org.id,
email: 'alice@acme.com',
role: 'member'
});

// Check user permissions
const canEdit = await client.companies.hasPermission({
userId: 'user-456',
orgId: org.id,
permission: 'projects:write'
});

Authentication

All API requests require authentication via API key headers:

curl -X GET "https://companies.api.us.23blocks.com/v1/companies/organizations" \
-H "Content-Type: application/json" \
-H "X-App-Id: your-app-id" \
-H "X-Api-Key: your-api-key"

Organizations API

List Organizations

Retrieve organizations the authenticated user belongs to.

GET /v1/companies/organizations

Query Parameters:

ParameterTypeDescription
rolestringFilter by user's role in organization
statusstringFilter by status: active, suspended, deleted
pageintegerPage number (default: 1)
per_pageintegerItems per page (default: 20, max: 100)

Response:

{
"data": [
{
"id": "org-abc123",
"type": "organization",
"attributes": {
"name": "Acme Corporation",
"slug": "acme",
"logo_url": "https://cdn.23blocks.com/orgs/acme/logo.png",
"status": "active",
"member_count": 45,
"plan": "enterprise",
"settings": {
"allow_domain_join": true,
"require_2fa": true,
"default_role": "member"
},
"verified_domains": ["acme.com", "acme.io"],
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-03-20T14:30:00Z"
},
"relationships": {
"owner": {
"data": { "type": "user", "id": "user-owner" }
}
}
}
],
"meta": {
"total": 3,
"page": 1,
"per_page": 20
}
}

Create Organization

Create a new organization.

POST /v1/companies/organizations

Request Body:

{
"data": {
"type": "organization",
"attributes": {
"name": "TechStart Inc",
"slug": "techstart",
"owner_id": "user-123",
"settings": {
"allow_domain_join": true,
"require_2fa": false,
"default_role": "member"
}
}
}
}

Get Organization

Retrieve a specific organization.

GET /v1/companies/organizations/{org_id}

Query Parameters:

ParameterTypeDescription
includestringInclude related resources: members, teams, roles

Update Organization

Update organization details.

PATCH /v1/companies/organizations/{org_id}

Request Body:

{
"data": {
"type": "organization",
"attributes": {
"name": "Acme Corp (Updated)",
"settings": {
"require_2fa": true,
"session_timeout_minutes": 60
}
}
}
}

Delete Organization

Soft delete an organization.

DELETE /v1/companies/organizations/{org_id}

Members API

List Members

Get all members of an organization.

GET /v1/companies/organizations/{org_id}/members

Query Parameters:

ParameterTypeDescription
rolestringFilter by role ID
team_idstringFilter by team
statusstringFilter: active, invited, suspended
searchstringSearch by name or email

Response:

{
"data": [
{
"id": "member-xyz",
"type": "membership",
"attributes": {
"user_id": "user-456",
"status": "active",
"joined_at": "2025-02-01T10:00:00Z",
"last_active_at": "2025-03-20T15:30:00Z"
},
"relationships": {
"user": {
"data": { "type": "user", "id": "user-456" }
},
"role": {
"data": { "type": "role", "id": "role-admin" }
},
"teams": {
"data": [
{ "type": "team", "id": "team-engineering" }
]
}
},
"included": {
"user": {
"id": "user-456",
"name": "Alice Smith",
"email": "alice@acme.com",
"avatar_url": "https://..."
}
}
}
]
}

Add Member

Add an existing user to an organization.

POST /v1/companies/organizations/{org_id}/members

Request Body:

{
"data": {
"type": "membership",
"attributes": {
"user_id": "user-789",
"role_id": "role-member",
"team_ids": ["team-engineering"]
}
}
}

Update Member

Update member's role or teams.

PATCH /v1/companies/organizations/{org_id}/members/{member_id}

Request Body:

{
"data": {
"type": "membership",
"attributes": {
"role_id": "role-admin",
"team_ids": ["team-engineering", "team-devops"]
}
}
}

Remove Member

Remove a member from the organization.

DELETE /v1/companies/organizations/{org_id}/members/{member_id}

Suspend Member

Temporarily suspend member access.

POST /v1/companies/organizations/{org_id}/members/{member_id}/suspend

Invitations API

List Invitations

Get pending invitations for an organization.

GET /v1/companies/organizations/{org_id}/invitations

Response:

{
"data": [
{
"id": "invite-abc",
"type": "invitation",
"attributes": {
"email": "bob@example.com",
"role_id": "role-member",
"status": "pending",
"expires_at": "2025-04-01T00:00:00Z",
"invited_by": "user-123",
"created_at": "2025-03-20T10:00:00Z"
}
}
]
}

Send Invitation

Invite a user to join the organization.

POST /v1/companies/organizations/{org_id}/invitations

Request Body:

{
"data": {
"type": "invitation",
"attributes": {
"email": "carol@example.com",
"role_id": "role-member",
"team_ids": ["team-sales"],
"message": "Welcome to our team!",
"expires_in_days": 7
}
}
}

Bulk Invite

Send multiple invitations at once.

POST /v1/companies/organizations/{org_id}/invitations/bulk

Request Body:

{
"invitations": [
{ "email": "user1@example.com", "role_id": "role-member" },
{ "email": "user2@example.com", "role_id": "role-member" },
{ "email": "user3@example.com", "role_id": "role-admin" }
],
"team_ids": ["team-engineering"],
"message": "Join our engineering team!"
}

Accept Invitation

Accept an invitation (called by invited user).

POST /v1/companies/invitations/{invitation_token}/accept

Revoke Invitation

Cancel a pending invitation.

DELETE /v1/companies/organizations/{org_id}/invitations/{invitation_id}

Resend Invitation

Resend invitation email.

POST /v1/companies/organizations/{org_id}/invitations/{invitation_id}/resend

Teams API

List Teams

Get all teams in an organization.

GET /v1/companies/organizations/{org_id}/teams

Response:

{
"data": [
{
"id": "team-engineering",
"type": "team",
"attributes": {
"name": "Engineering",
"description": "Product development team",
"member_count": 12,
"parent_id": null,
"settings": {
"private": false,
"auto_add_new_members": false
},
"created_at": "2025-01-20T10:00:00Z"
}
},
{
"id": "team-frontend",
"type": "team",
"attributes": {
"name": "Frontend",
"description": "Frontend developers",
"member_count": 5,
"parent_id": "team-engineering",
"created_at": "2025-01-22T10:00:00Z"
}
}
]
}

Create Team

POST /v1/companies/organizations/{org_id}/teams

Request Body:

{
"data": {
"type": "team",
"attributes": {
"name": "Backend",
"description": "Backend developers",
"parent_id": "team-engineering",
"settings": {
"private": false
}
}
}
}

Add Team Members

POST /v1/companies/organizations/{org_id}/teams/{team_id}/members

Request Body:

{
"user_ids": ["user-123", "user-456", "user-789"]
}

Remove Team Member

DELETE /v1/companies/organizations/{org_id}/teams/{team_id}/members/{user_id}

Roles API

List Roles

Get all roles in an organization.

GET /v1/companies/organizations/{org_id}/roles

Response:

{
"data": [
{
"id": "role-owner",
"type": "role",
"attributes": {
"name": "Owner",
"description": "Full organization access",
"system": true,
"member_count": 1,
"permissions": ["*"]
}
},
{
"id": "role-admin",
"type": "role",
"attributes": {
"name": "Admin",
"description": "Administrative access",
"system": true,
"member_count": 3,
"permissions": [
"members:read", "members:write", "members:invite",
"teams:read", "teams:write",
"roles:read",
"settings:read", "settings:write",
"billing:read"
]
}
},
{
"id": "role-member",
"type": "role",
"attributes": {
"name": "Member",
"description": "Standard member access",
"system": true,
"member_count": 41,
"permissions": [
"members:read",
"teams:read",
"projects:read", "projects:write"
]
}
},
{
"id": "role-pm",
"type": "role",
"attributes": {
"name": "Project Manager",
"description": "Custom role for project managers",
"system": false,
"member_count": 5,
"permissions": [
"members:read",
"teams:read",
"projects:read", "projects:write", "projects:delete",
"tasks:read", "tasks:write", "tasks:assign"
]
}
}
]
}

Create Custom Role

POST /v1/companies/organizations/{org_id}/roles

Request Body:

{
"data": {
"type": "role",
"attributes": {
"name": "Developer",
"description": "Development team member",
"permissions": [
"members:read",
"teams:read",
"projects:read", "projects:write",
"code:read", "code:write", "code:deploy"
]
}
}
}

Update Role

PATCH /v1/companies/organizations/{org_id}/roles/{role_id}

Delete Role

DELETE /v1/companies/organizations/{org_id}/roles/{role_id}

List Available Permissions

Get all available permissions for role configuration.

GET /v1/companies/permissions

Response:

{
"data": {
"categories": [
{
"name": "Members",
"permissions": [
{ "key": "members:read", "description": "View organization members" },
{ "key": "members:write", "description": "Edit member details" },
{ "key": "members:invite", "description": "Invite new members" },
{ "key": "members:remove", "description": "Remove members" }
]
},
{
"name": "Teams",
"permissions": [
{ "key": "teams:read", "description": "View teams" },
{ "key": "teams:write", "description": "Create and edit teams" },
{ "key": "teams:delete", "description": "Delete teams" }
]
},
{
"name": "Billing",
"permissions": [
{ "key": "billing:read", "description": "View billing information" },
{ "key": "billing:write", "description": "Manage subscriptions and payments" }
]
}
]
}
}

Permissions API

Check Permission

Check if a user has a specific permission.

GET /v1/companies/organizations/{org_id}/permissions/check

Query Parameters:

ParameterTypeDescription
user_idstringUser to check
permissionstringPermission key to check
resource_idstringOptional resource ID for resource-level permissions

Response:

{
"data": {
"user_id": "user-123",
"permission": "projects:write",
"allowed": true,
"source": "role:project-manager"
}
}

Batch Check Permissions

Check multiple permissions at once.

POST /v1/companies/organizations/{org_id}/permissions/batch-check

Request Body:

{
"user_id": "user-123",
"permissions": [
"projects:read",
"projects:write",
"projects:delete",
"billing:read"
]
}

Response:

{
"data": {
"user_id": "user-123",
"results": {
"projects:read": true,
"projects:write": true,
"projects:delete": false,
"billing:read": false
}
}
}

Domain Verification API

List Verified Domains

GET /v1/companies/organizations/{org_id}/domains

Response:

{
"data": [
{
"id": "domain-abc",
"type": "domain",
"attributes": {
"domain": "acme.com",
"status": "verified",
"verification_method": "dns",
"auto_join_enabled": true,
"verified_at": "2025-02-01T10:00:00Z"
}
}
]
}

Add Domain

Start domain verification process.

POST /v1/companies/organizations/{org_id}/domains

Request Body:

{
"data": {
"type": "domain",
"attributes": {
"domain": "newdomain.com",
"verification_method": "dns"
}
}
}

Response:

{
"data": {
"id": "domain-xyz",
"type": "domain",
"attributes": {
"domain": "newdomain.com",
"status": "pending",
"verification_method": "dns",
"verification_record": {
"type": "TXT",
"name": "_23blocks-verification",
"value": "23blocks-verify=abc123xyz"
}
}
}
}

Verify Domain

Trigger domain verification check.

POST /v1/companies/organizations/{org_id}/domains/{domain_id}/verify

Remove Domain

DELETE /v1/companies/organizations/{org_id}/domains/{domain_id}

SSO Configuration API

Get SSO Configuration

GET /v1/companies/organizations/{org_id}/sso

Response:

{
"data": {
"enabled": true,
"provider": "saml",
"enforce_sso": true,
"saml_config": {
"idp_entity_id": "https://idp.acme.com",
"idp_sso_url": "https://idp.acme.com/sso",
"idp_certificate": "-----BEGIN CERTIFICATE-----...",
"sp_entity_id": "https://app.23blocks.com/sso/acme",
"sp_acs_url": "https://app.23blocks.com/sso/acme/acs"
},
"attribute_mapping": {
"email": "user.email",
"first_name": "user.firstName",
"last_name": "user.lastName",
"role": "user.role"
}
}
}

Configure SSO

PUT /v1/companies/organizations/{org_id}/sso

Request Body (SAML):

{
"data": {
"provider": "saml",
"enabled": true,
"enforce_sso": true,
"saml_config": {
"idp_entity_id": "https://idp.acme.com",
"idp_sso_url": "https://idp.acme.com/sso",
"idp_certificate": "-----BEGIN CERTIFICATE-----..."
},
"attribute_mapping": {
"email": "user.email",
"first_name": "user.firstName",
"last_name": "user.lastName"
}
}
}

Request Body (OIDC):

{
"data": {
"provider": "oidc",
"enabled": true,
"oidc_config": {
"issuer": "https://accounts.google.com",
"client_id": "your-client-id",
"client_secret": "your-client-secret"
}
}
}

Disable SSO

DELETE /v1/companies/organizations/{org_id}/sso

Multi-Tenancy

Tenant-Aware Requests

All API requests can be scoped to a specific organization using headers:

curl -X GET "https://companies.api.us.23blocks.com/v1/projects" \
-H "X-Organization-Id: org-abc123" \
-H "X-App-Id: your-app-id" \
-H "X-Api-Key: your-api-key"

Tenant Context in SDKs

// Set default organization context
client.setOrganization('org-abc123');

// All subsequent requests are scoped to this org
const projects = await client.projects.list();
const members = await client.companies.members.list();

// Or specify per-request
const otherProjects = await client.projects.list({
organizationId: 'org-xyz789'
});

Activity Logs API

List Activity

Get audit log for an organization.

GET /v1/companies/organizations/{org_id}/activity

Query Parameters:

ParameterTypeDescription
actor_idstringFilter by user who performed action
actionstringFilter by action type
resource_typestringFilter by resource type
fromdatetimeStart date
todatetimeEnd date

Response:

{
"data": [
{
"id": "activity-001",
"type": "activity",
"attributes": {
"action": "member.invited",
"actor_id": "user-123",
"actor_name": "Alice Smith",
"target_type": "invitation",
"target_id": "invite-abc",
"metadata": {
"email": "bob@example.com",
"role": "member"
},
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"created_at": "2025-03-20T15:30:00Z"
}
}
]
}

Activity Types

ActionDescription
organization.createdOrganization was created
organization.updatedOrganization settings changed
member.addedMember added to organization
member.removedMember removed from organization
member.role_changedMember role was changed
member.invitedInvitation sent
member.invitation_acceptedInvitation was accepted
team.createdTeam was created
team.deletedTeam was deleted
role.createdCustom role created
role.updatedRole permissions changed
sso.configuredSSO was configured
domain.verifiedDomain was verified

Webhooks

Subscribe to Companies Block events for real-time notifications.

Available Events

EventDescription
organization.createdNew organization created
organization.updatedOrganization settings changed
organization.deletedOrganization was deleted
member.addedMember joined organization
member.removedMember left or was removed
member.role_changedMember's role was updated
invitation.sentInvitation was sent
invitation.acceptedInvitation was accepted
invitation.declinedInvitation was declined
team.createdTeam was created
team.deletedTeam was deleted
role.createdCustom role was created
role.updatedRole was modified
domain.verifiedDomain verification completed
sso.configuredSSO settings changed

Webhook Payload Example

{
"event": "member.added",
"timestamp": "2025-03-20T16:30:00Z",
"data": {
"organization_id": "org-abc123",
"membership_id": "member-xyz",
"user_id": "user-456",
"role_id": "role-member",
"invited_by": "user-123",
"joined_at": "2025-03-20T16:30:00Z"
}
}

Configure Webhooks

POST /v1/companies/webhooks

Request Body:

{
"url": "https://your-app.com/webhooks/companies",
"events": ["member.added", "member.removed", "invitation.sent"],
"secret": "your-webhook-secret"
}

SDK Examples

JavaScript/TypeScript

import { create23BlocksClient } from '@23blocks/sdk';

const client = create23BlocksClient({
urls: { companies: 'https://companies.api.us.23blocks.com' },
appId: 'your-app-id',
apiKey: 'your-api-key',
});

// Create organization with teams
async function setupOrganization(userId: string) {
// Create org
const org = await client.companies.create({
name: 'TechCorp',
slug: 'techcorp',
ownerId: userId
});

// Create teams
const engineering = await client.companies.createTeam(org.id, {
name: 'Engineering',
description: 'Product development'
});

const frontend = await client.companies.createTeam(org.id, {
name: 'Frontend',
parentId: engineering.id
});

// Create custom role
await client.companies.createRole(org.id, {
name: 'Tech Lead',
permissions: [
'members:read',
'teams:read', 'teams:write',
'projects:*',
'deployments:*'
]
});

return org;
}

// Invite team members
async function inviteTeam(orgId: string, emails: string[]) {
const results = await client.companies.bulkInvite(orgId, {
emails,
roleId: 'role-member',
message: 'Welcome to the team!'
});

console.log(`Invited: ${results.successful.length}`);
console.log(`Failed: ${results.failed.length}`);

return results;
}

// Check permissions before action
async function canUserPerformAction(
orgId: string,
userId: string,
action: string
) {
const result = await client.companies.checkPermission({
organizationId: orgId,
userId,
permission: action
});

return result.allowed;
}

Python

from blocks_sdk import BlocksClient

client = BlocksClient(
base_url="https://companies.api.us.23blocks.com",
app_id="your-app-id",
api_key="your-api-key"
)

# Create and configure organization
def create_organization(name: str, owner_id: str):
org = client.companies.create(
name=name,
slug=name.lower().replace(" ", "-"),
owner_id=owner_id,
settings={
"allow_domain_join": True,
"require_2fa": True
}
)

# Add verified domain
domain = client.companies.add_domain(
org_id=org.id,
domain="techcorp.com",
verification_method="dns"
)

print(f"Add this TXT record: {domain['verification_record']['value']}")

return org

# Get organization members with their roles
def get_members_with_roles(org_id: str):
members = client.companies.list_members(
org_id=org_id,
include=["role", "teams"]
)

for member in members:
print(f"{member['user']['name']} - {member['role']['name']}")
print(f" Teams: {[t['name'] for t in member['teams']]}")

return members

# Enforce permissions in your application
def with_permission(org_id: str, permission: str):
def decorator(func):
def wrapper(user_id, *args, **kwargs):
result = client.companies.check_permission(
org_id=org_id,
user_id=user_id,
permission=permission
)

if not result["allowed"]:
raise PermissionError(f"Missing permission: {permission}")

return func(user_id, *args, **kwargs)
return wrapper
return decorator

@with_permission("org-abc", "projects:delete")
def delete_project(user_id: str, project_id: str):
# User has permission, proceed with deletion
pass

Rate Limits

EndpointRate Limit
Organization CRUD100 requests/minute
Member operations200 requests/minute
Invitation sending50 requests/minute
Permission checks1000 requests/minute
Activity logs30 requests/minute

Error Handling

All errors follow JSON:API error format:

{
"errors": [
{
"status": "403",
"code": "permission_denied",
"title": "Permission Denied",
"detail": "You do not have permission to invite members to this organization.",
"source": { "pointer": "/data/attributes/email" }
}
]
}

Common Error Codes

CodeDescription
organization_not_foundOrganization does not exist
member_not_foundMember does not exist in organization
already_memberUser is already a member
invitation_expiredInvitation has expired
permission_deniedUser lacks required permission
role_not_foundRole does not exist
domain_already_claimedDomain is claimed by another organization
sso_requiredSSO authentication is required for this organization

Next Steps