Skip to main content

Organizations API

Complete reference for managing organizations, team members, site access, and invitations in SealMetrics multi-tenancy system.

Overview

The Organizations API allows you to:

  • Create and manage organizations (workspaces)
  • Invite team members and manage their roles
  • Control site-level access for team members
  • List and manage organization sites

Base path: /organizations


Understanding Multi-Tenancy

SealMetrics uses a three-level access model:

Users <--M:N--> Organizations <--1:N--> Sites
  • Users can belong to multiple organizations
  • Organizations contain multiple sites (analytics accounts)
  • Members have roles that determine their permissions

Organization Roles

RoleSites AccessMember ManagementBillingOrg Settings
ownerAll org sitesFull (add/remove/role change)YesFull (rename, delete)
adminAll org sitesAdd/remove members (except owners)NoView only
memberOnly assigned sitesView onlyNoNo

Organization Endpoints

List Organizations

GET /organizations

Returns all organizations the current user belongs to.

Response:

{
"success": true,
"data": {
"organizations": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "ACME Corporation",
"slug": "acme-corporation",
"is_active": true,
"member_count": 5,
"site_count": 3,
"user_role": "owner",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2025-01-08T09:15:00Z"
}
],
"total": 1,
"can_create_org": true
}
}
FieldDescription
can_create_orgtrue if user can create a new organization
user_roleCurrent user's role in this organization

Create Organization

POST /organizations

Creates a new organization. The creator becomes the owner.

Required scope: write

Request Body:

{
"name": "My Company"
}
FieldTypeRequiredDescription
namestringYesOrganization name (1-255 chars)

Response (201 Created):

{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Company",
"slug": "my-company",
"is_active": true,
"member_count": 1,
"site_count": 0,
"user_role": "owner",
"created_at": "2025-01-10T14:30:00Z",
"updated_at": "2025-01-10T14:30:00Z"
}
}

Note: The slug is auto-generated from the name and used in URLs.


Get Organization

GET /organizations/{slug}

Returns detailed information about a specific organization.

Path Parameters:

ParameterTypeDescription
slugstringOrganization slug (from URL)

Response:

{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "ACME Corporation",
"slug": "acme-corporation",
"is_active": true,
"member_count": 5,
"site_count": 3,
"user_role": "admin",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2025-01-08T09:15:00Z"
}
}

Update Organization

PATCH /organizations/{slug}

Updates organization settings. Only owners can update.

Required role: owner

Request Body:

{
"name": "ACME Corp (Renamed)"
}

Response:

{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "ACME Corp (Renamed)",
"slug": "acme-corporation",
"is_active": true,
"member_count": 5,
"site_count": 3,
"user_role": "owner",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2025-01-10T15:00:00Z"
}
}

Delete Organization

DELETE /organizations/{slug}

Soft deletes an organization and all its sites. Requires name confirmation.

Required role: owner

Request Body:

{
"name": "ACME Corporation"
}
FieldTypeDescription
namestringType the exact organization name to confirm deletion

Response:

{
"success": true,
"data": {
"message": "Organization deleted successfully"
}
}

Warning: This action deactivates the organization and all associated sites. Data is retained but inaccessible.


Member Endpoints

List Members

GET /organizations/{slug}/members

Returns all members of the organization.

Response:

{
"success": true,
"data": {
"members": [
{
"user_id": 1,
"email": "owner@acme.com",
"name": "John Owner",
"role": "owner",
"site_count": 3,
"created_at": "2024-01-15T10:00:00Z"
},
{
"user_id": 2,
"email": "analyst@acme.com",
"name": "Jane Analyst",
"role": "member",
"site_count": 1,
"created_at": "2024-03-20T14:00:00Z"
}
],
"total": 2
}
}
FieldDescription
site_countNumber of sites this member has access to

Add Member

POST /organizations/{slug}/members

Adds an existing user as a member of the organization.

Required role: admin or owner

Request Body:

{
"user_id": 5,
"role": "member"
}
FieldTypeRequiredDescription
user_idintegerYesUser ID to add
roleenumNoowner, admin, or member (default: member)

Response (201 Created):

{
"success": true,
"data": {
"message": "Member added successfully"
}
}

Note: To invite users who don't have an account yet, use the Invitations API.


Update Member Role

PATCH /organizations/{slug}/members/{user_id}

Changes a member's role in the organization.

Required role: admin or owner

Request Body:

{
"role": "admin"
}

Response:

{
"success": true,
"data": {
"message": "Role updated successfully"
}
}

Restrictions:

  • Admins cannot change owner roles
  • Cannot demote the last owner

Remove Member

DELETE /organizations/{slug}/members/{user_id}

Removes a member from the organization.

Required role: admin or owner

Response:

{
"success": true,
"data": {
"message": "Member removed successfully"
}
}

Restrictions:

  • Cannot remove the last owner
  • Admins cannot remove owners

Organization Sites

List Organization Sites

GET /organizations/{slug}/sites

Returns all sites belonging to the organization.

Response:

{
"success": true,
"data": {
"sites": [
{
"id": "acme-main",
"name": "ACME Main Website",
"is_active": true,
"timezone": "Europe/Madrid",
"currency": "EUR",
"created_at": "2024-01-15T10:00:00Z"
},
{
"id": "acme-shop",
"name": "ACME E-commerce",
"is_active": true,
"timezone": "Europe/Madrid",
"currency": "EUR",
"created_at": "2024-02-01T09:00:00Z"
}
],
"total": 2
}
}

Member Site Access

Control which sites a member can access. Only applies to members with the member role (owners and admins have access to all sites automatically).

List Member's Sites

GET /organizations/{slug}/members/{user_id}/sites

Returns sites assigned to a specific member.

Required role: admin or owner

Response:

{
"success": true,
"data": {
"sites": [
{
"id": "acme-main",
"name": "ACME Main Website",
"has_access": true
},
{
"id": "acme-shop",
"name": "ACME E-commerce",
"has_access": false
}
],
"total": 2
}
}

Add Site Access

POST /organizations/{slug}/members/{user_id}/sites

Grants a member access to one or more sites.

Required role: admin or owner

Request Body:

{
"site_ids": ["acme-main", "acme-shop"]
}

Response:

{
"success": true,
"data": {
"added": ["acme-main", "acme-shop"],
"already_assigned": [],
"invalid": []
}
}
FieldDescription
addedSites successfully granted access
already_assignedSites the member already had access to
invalidSite IDs that don't exist or don't belong to the org

Remove Site Access

DELETE /organizations/{slug}/members/{user_id}/sites/{site_id}

Removes a member's access to a specific site.

Required role: admin or owner

Response:

{
"success": true,
"data": {
"message": "Site access removed successfully"
}
}

Invitation Endpoints

Invitations allow you to add team members who don't have a SealMetrics account yet.

List Invitations

GET /organizations/{slug}/invitations

Returns pending invitations for the organization.

Required role: admin or owner

Response:

{
"success": true,
"data": {
"invitations": [
{
"id": 1,
"org_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "newuser@example.com",
"role": "member",
"invited_by_name": "John Owner",
"is_active": true,
"expires_at": "2025-01-17T14:30:00Z",
"accepted_at": null,
"created_at": "2025-01-10T14:30:00Z"
}
],
"total": 1
}
}

Send Invitation

POST /organizations/{slug}/invitations

Sends an email invitation to join the organization.

Required role: admin or owner

Request Body:

{
"email": "newuser@example.com",
"role": "member",
"site_ids": ["acme-main"]
}
FieldTypeRequiredDescription
emailstringYesEmail address to invite
roleenumNoowner, admin, or member (default: member)
site_idsstring[]NoPre-assign sites for member role (ignored for owner/admin)

Response (201 Created):

{
"success": true,
"data": {
"invitation_id": 1,
"invitation_link": "https://my.sealmetrics.com/accept-invitation?token=abc123...",
"expires_at": "2025-01-17T14:30:00Z"
}
}

Notes:

  • Invitation expires in 7 days
  • If user already has an account, they can accept while logged in
  • If user is new, they'll create an account during acceptance

Resend Invitation

POST /organizations/{slug}/invitations/{invitation_id}/resend

Resends the invitation email with a new token.

Required role: admin or owner

Response:

{
"success": true,
"data": {
"message": "Invitation resent"
}
}

Revoke Invitation

DELETE /organizations/{slug}/invitations/{invitation_id}

Cancels a pending invitation.

Required role: admin or owner

Response:

{
"success": true,
"data": {
"message": "Invitation revoked"
}
}

Accept Invitation Endpoints

These endpoints are called when a user clicks the invitation link.

Accept Invitation (New User)

POST /invitations/accept

Creates a new account and joins the organization.

Request Body:

{
"token": "abc123...",
"name": "Jane Doe",
"password": "<your_password>"
}
FieldTypeRequiredDescription
tokenstringYesInvitation token from the link
namestringYesUser's full name (1-255 chars)
passwordstringYesPassword (min 8 chars)

Response:

{
"success": true,
"data": {
"message": "Invitation accepted",
"organization": {
"name": "ACME Corporation",
"slug": "acme-corporation"
}
}
}

Accept Invitation (Existing User)

POST /invitations/accept-existing

Joins the organization with an existing logged-in account.

Requires: Authentication (JWT or API Key)

Request Body:

{
"token": "abc123..."
}

Response:

{
"success": true,
"data": {
"message": "Invitation accepted",
"organization": {
"name": "ACME Corporation",
"slug": "acme-corporation"
}
}
}

Error Codes

HTTP CodeError CodeDescription
400last_ownerCannot remove or demote the last owner
400confirmation_failedOrganization name doesn't match for deletion
403org_creation_not_allowedUser cannot create more organizations
403insufficient_permissionsUser's role doesn't allow this action
404org_not_foundOrganization doesn't exist or user has no access
404member_not_foundUser is not a member of this organization
404invitation_not_foundInvitation doesn't exist or is expired
409member_already_existsUser is already a member of this organization
409user_already_memberInvited user is already a member

Code Examples

Python

import requests

API_KEY = "sm_your_api_key"
BASE_URL = "https://api.sealmetrics.com/api/v1"
headers = {"X-API-Key": API_KEY}

# List my organizations
orgs = requests.get(
f"{BASE_URL}/organizations",
headers=headers
).json()["data"]["organizations"]

print(f"My organizations: {[o['name'] for o in orgs]}")

# Create organization
new_org = requests.post(
f"{BASE_URL}/organizations",
headers=headers,
json={"name": "My New Team"}
).json()["data"]

print(f"Created org: {new_org['slug']}")

# Invite a team member
invitation = requests.post(
f"{BASE_URL}/organizations/{new_org['slug']}/invitations",
headers=headers,
json={
"email": "colleague@example.com",
"role": "member",
"site_ids": ["my-website"]
}
).json()["data"]

print(f"Invitation link: {invitation['invitation_link']}")

# List members
members = requests.get(
f"{BASE_URL}/organizations/{new_org['slug']}/members",
headers=headers
).json()["data"]["members"]

for member in members:
print(f" {member['name']} ({member['role']})")

JavaScript

const API_KEY = 'sm_your_api_key';
const BASE_URL = 'https://api.sealmetrics.com/api/v1';

const headers = {
'X-API-Key': API_KEY,
'Content-Type': 'application/json'
};

// List my organizations
const orgsResponse = await fetch(`${BASE_URL}/organizations`, { headers });
const { data: { organizations } } = await orgsResponse.json();
console.log('My orgs:', organizations.map(o => o.name));

// Create organization
const createResponse = await fetch(`${BASE_URL}/organizations`, {
method: 'POST',
headers,
body: JSON.stringify({ name: 'My New Team' })
});
const { data: newOrg } = await createResponse.json();
console.log(`Created: ${newOrg.slug}`);

// Invite team member with pre-assigned sites
const inviteResponse = await fetch(
`${BASE_URL}/organizations/${newOrg.slug}/invitations`,
{
method: 'POST',
headers,
body: JSON.stringify({
email: 'colleague@example.com',
role: 'member',
site_ids: ['my-website', 'my-blog']
})
}
);
const { data: invitation } = await inviteResponse.json();
console.log(`Invitation sent! Link: ${invitation.invitation_link}`);

// Grant site access to existing member
await fetch(
`${BASE_URL}/organizations/${newOrg.slug}/members/123/sites`,
{
method: 'POST',
headers,
body: JSON.stringify({ site_ids: ['new-site'] })
}
);

PHP

<?php
$apiKey = 'sm_your_api_key';
$baseUrl = 'https://api.sealmetrics.com/api/v1';

$headers = [
'X-API-Key: ' . $apiKey,
'Content-Type: application/json'
];

// Create organization
$ch = curl_init("$baseUrl/organizations");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'name' => 'My Company'
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
$org = $response['data'];
curl_close($ch);

echo "Created organization: " . $org['slug'] . "\n";

// Invite member
$ch = curl_init("$baseUrl/organizations/{$org['slug']}/invitations");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'email' => 'team@example.com',
'role' => 'admin'
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

echo "Invitation link: " . $response['data']['invitation_link'] . "\n";
?>

Best Practices

Team Structure

  1. Keep one owner per organization - Designate a primary account owner
  2. Use admin role sparingly - Admins can manage all members and sites
  3. Assign specific sites to members - Use site_ids when inviting members

Invitation Workflow

  1. Always pre-assign sites when inviting with member role
  2. Invitations expire in 7 days - resend if needed
  3. Check can_create_org before showing "Create Organization" UI

Security

  • Revoke access immediately when team members leave
  • Periodically audit member list and site access
  • Use the audit logs to track changes (see Audit Log)