Skip to main content

Default Integration

The Default integration allows you to connect any CRM or external system to SalesOS — even if there is no dedicated integration available. You send numbered activities (001-999) via API, and map them to SalesOS missions in the Dashboard.

How It Works

  1. You get an API Key from the SalesOS Dashboard (Admin > Integrations > API Keys)
  2. You sync your team — register collaborators (salespeople) so SalesOS knows who they are
  3. You send activities numbered 001-999 via a single REST endpoint
  4. Your admin maps each activity number to a SalesOS mission in the Dashboard
  5. SalesOS processes the activities as mission completions, awarding points and tracking progress
Activity codes are just numbers (001 through 999). The meaning of each code is defined by your team when mapping them to missions in the Dashboard. For example, “001” could mean “Phone call” and “002” could mean “Meeting scheduled”.

Quick Start

1

Get your API Key

Go to Admin > Integrations > API Keys in the SalesOS Dashboard. Create a new key with the default:sync scope. Copy the key — it will only be shown once.Your key looks like: sk_live_a1b2c3d4e5f6g7h8i9j0...
2

Register your sales team

Before sending activities, tell SalesOS who your salespeople are:
curl -X POST https://api.play2sell.com/functions/v1/default-integration \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "sync_collaborators",
    "collaborators": [
      {
        "external_id": "emp-101",
        "name": "Maria Santos",
        "email": "maria@yourcompany.com",
        "team": "Sales Team A",
        "role": "sales_rep"
      },
      {
        "external_id": "emp-102",
        "name": "Joao Silva",
        "email": "joao@yourcompany.com",
        "team": "Sales Team A",
        "role": "sales_rep"
      },
      {
        "external_id": "emp-103",
        "name": "Ana Oliveira",
        "email": "ana@yourcompany.com",
        "team": "Sales Team B",
        "role": "team_lead"
      }
    ]
  }'
Response:
{
  "data": { "created": 3, "existing": 0, "errors": [], "total": 3 },
  "meta": { "request_id": "a1b2c3d4-...", "timestamp": "2026-03-17T10:00:00.000Z" }
}
3

Send activities from your CRM

Now send the activities your salespeople performed today:
curl -X POST https://api.play2sell.com/functions/v1/default-integration \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "sync_activities",
    "activities": [
      {
        "activity_code": "001",
        "external_id": "crm-evt-10001",
        "collaborator_email": "maria@yourcompany.com",
        "data": { "client": "Acme Corp", "duration_min": 12 },
        "occurred_at": "2026-03-17T09:15:00Z"
      },
      {
        "activity_code": "002",
        "external_id": "crm-evt-10002",
        "collaborator_email": "joao@yourcompany.com",
        "data": { "client": "Beta Inc", "type": "video_call" },
        "occurred_at": "2026-03-17T10:30:00Z"
      },
      {
        "activity_code": "001",
        "external_id": "crm-evt-10003",
        "collaborator_email": "maria@yourcompany.com",
        "data": { "client": "Gamma Ltd" },
        "occurred_at": "2026-03-17T11:00:00Z"
      }
    ]
  }'
Response:
{
  "data": { "processed": 3, "skipped": 0, "duplicates": 0, "errors": [], "total": 3 },
  "meta": { "request_id": "d4e5f6g7-...", "timestamp": "2026-03-17T11:05:00.000Z" }
}
Maria got 2 activities (two phone calls) and Joao got 1 (a meeting).
4

Map codes to missions in the Dashboard

In the Dashboard, go to Missions > Configure. Select “Default” from the CRM dropdown. You will see activities 001 through 999. Map the ones you use:
ActivityMap to Mission
Atividade 001Ligacoes Realizadas
Atividade 002Reunioes Agendadas
Atividade 003Propostas Enviadas
Click Save. From now on, every activity “001” sent via API will count toward the “Ligacoes Realizadas” mission.

Authentication

All requests require an API Key in the Authorization header:
Authorization: Bearer sk_live_YOUR_API_KEY
See the Authentication page for details on creating and managing API Keys.
PropertyDetails
HeaderAuthorization: Bearer sk_live_xxx
Scope requireddefault:sync
Rate limitConfigurable per key (default: 1000 requests/hour)
Key formatsk_live_ (production) or sk_test_ (testing)

Environments

Base URL: https://api.play2sell.comDashboard: https://dashboard.play2sell.comApp: https://app.play2sell.com

Endpoint Reference

Base URL:
POST https://api.play2sell.com/functions/v1/default-integration
The endpoint accepts two actions via the action field: sync_collaborators and sync_activities.

Action: sync_collaborators

Register or update collaborators (salespeople) in SalesOS. Collaborators must exist before you can attribute activities to them.

Request Schema

action
string
required
Must be "sync_collaborators"
collaborators
array
required
Array of collaborator objects (max 500)
collaborators[].external_id
string
required
Unique ID in your system (max 255 chars)
collaborators[].name
string
required
Full name (max 255 chars)
collaborators[].email
string
required
Valid email — used to match activities later
collaborators[].phone
string
Phone number (any format)
collaborators[].document
string
ID document like CPF (max 20 chars)
collaborators[].team
string
Team name (max 100 chars)
collaborators[].role
string
Role in your org (max 50 chars)
collaborators[].metadata
object
Any extra key-value data

Example: Sync a full team

curl -X POST https://api.play2sell.com/functions/v1/default-integration \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "sync_collaborators",
    "collaborators": [
      {
        "external_id": "emp-101",
        "name": "Maria Santos",
        "email": "maria@yourcompany.com",
        "phone": "+5511999001001",
        "document": "12345678901",
        "team": "Equipe Vendas SP",
        "role": "vendedor",
        "metadata": { "crm_id": "CRM-2001", "hire_date": "2024-06-15" }
      },
      {
        "external_id": "emp-102",
        "name": "Joao Silva",
        "email": "joao@yourcompany.com",
        "phone": "+5511999002002",
        "team": "Equipe Vendas SP",
        "role": "vendedor"
      },
      {
        "external_id": "emp-103",
        "name": "Ana Oliveira",
        "email": "ana@yourcompany.com",
        "team": "Equipe Vendas RJ",
        "role": "gerente",
        "metadata": { "crm_id": "CRM-2003", "is_manager": true }
      }
    ]
  }'

Response (200 — Success)

{
  "data": {
    "created": 2,
    "existing": 1,
    "errors": [],
    "total": 3
  },
  "meta": {
    "request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "timestamp": "2026-03-17T14:30:00.000Z"
  }
}
  • created: 2 — Maria and Joao were new, so they were created
  • existing: 1 — Ana already existed (matched by email), so she was updated
  • errors: [] — no failures in this batch

Example: Partial failures in a batch

If some collaborators have invalid data, they fail individually without blocking the rest:
{
  "data": {
    "created": 8,
    "existing": 1,
    "errors": [
      "Collaborator emp-110 (invalid-email): Invalid email format"
    ],
    "total": 10
  },
  "meta": {
    "request_id": "b2c3d4e5-...",
    "timestamp": "2026-03-17T14:30:00.000Z"
  }
}
Re-syncing is safe. You can send the same collaborators multiple times. Existing ones are updated (not duplicated) based on their email. This makes it easy to run a nightly full sync from your CRM.

Action: sync_activities

Send activity events attributed to collaborators. Each activity uses a 3-digit code (001-999) that you map to missions in the Dashboard.

Request Schema

action
string
required
Must be "sync_activities"
activities
array
required
Array of activity objects (max 1000)
activities[].activity_code
string
required
3-digit code: "001" through "999"
activities[].external_id
string
required
Unique ID for deduplication (max 255 chars)
activities[].collaborator_email
string
required
Email of the salesperson who did the activity
activities[].data
object
Any additional context (free-form)
activities[].occurred_at
string
When it happened (ISO 8601). Defaults to now.

Example: Send a day’s worth of activities

curl -X POST https://api.play2sell.com/functions/v1/default-integration \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "sync_activities",
    "activities": [
      {
        "activity_code": "001",
        "external_id": "crm-20260317-001",
        "collaborator_email": "maria@yourcompany.com",
        "data": {
          "client_name": "Acme Corp",
          "client_phone": "+5511988887777",
          "duration_minutes": 12,
          "outcome": "interested"
        },
        "occurred_at": "2026-03-17T09:15:00-03:00"
      },
      {
        "activity_code": "001",
        "external_id": "crm-20260317-002",
        "collaborator_email": "maria@yourcompany.com",
        "data": {
          "client_name": "Beta Inc",
          "duration_minutes": 8,
          "outcome": "no_answer"
        },
        "occurred_at": "2026-03-17T09:30:00-03:00"
      },
      {
        "activity_code": "002",
        "external_id": "crm-20260317-003",
        "collaborator_email": "joao@yourcompany.com",
        "data": {
          "client_name": "Gamma Ltd",
          "meeting_type": "video_call",
          "duration_minutes": 45
        },
        "occurred_at": "2026-03-17T10:00:00-03:00"
      },
      {
        "activity_code": "003",
        "external_id": "crm-20260317-004",
        "collaborator_email": "joao@yourcompany.com",
        "data": {
          "client_name": "Gamma Ltd",
          "proposal_value": 25000.00,
          "currency": "BRL"
        },
        "occurred_at": "2026-03-17T14:00:00-03:00"
      },
      {
        "activity_code": "001",
        "external_id": "crm-20260317-005",
        "collaborator_email": "ana@yourcompany.com",
        "data": {
          "client_name": "Delta SA",
          "duration_minutes": 20,
          "outcome": "scheduled_meeting"
        },
        "occurred_at": "2026-03-17T15:30:00-03:00"
      }
    ]
  }'

Response (200 — Success)

{
  "data": {
    "processed": 5,
    "skipped": 0,
    "duplicates": 0,
    "errors": [],
    "total": 5
  },
  "meta": {
    "request_id": "c3d4e5f6-7890-abcd-ef01-234567890abc",
    "timestamp": "2026-03-17T16:00:00.000Z"
  }
}

Response fields explained

processed
integer
Activities successfully recorded
skipped
integer
Activities where the collaborator email was not found in SalesOS
duplicates
integer
Activities with an external_id that was already sent before
errors
array
Array of error messages for items that failed
total
integer
Total items received in the request

Example: Mixed results (some skipped, some duplicates)

If you re-send the same batch or include unknown emails:
{
  "data": {
    "processed": 2,
    "skipped": 1,
    "duplicates": 2,
    "errors": [],
    "total": 5
  },
  "meta": {
    "request_id": "d4e5f6g7-...",
    "timestamp": "2026-03-17T16:05:00.000Z"
  }
}
  • skipped: 1 — one email was not registered via sync_collaborators
  • duplicates: 2 — two activities had external_id values already in the system

Activity Codes (001-999)

Activity codes are abstract identifiers. Their meaning is entirely up to you. Example mapping for a real estate company:
CodeCRM ActivitySalesOS MissionPoints
001Phone call to prospectLigacoes Realizadas5 pts
002Meeting (in-person or video)Reunioes Agendadas15 pts
003Proposal sentPropostas Enviadas20 pts
004Contract signedContratos Fechados50 pts
005Property visit with clientVisitas Realizadas25 pts
006Follow-up email sentFollow-ups Enviados3 pts
007Lead qualification completedLeads Qualificados10 pts
Example mapping for a SaaS company:
CodeCRM ActivitySalesOS MissionPoints
001Discovery callLigacoes de Descoberta10 pts
002Product demo scheduledDemos Agendadas20 pts
003Trial startedTrials Iniciados15 pts
004Proposal sentPropostas Enviadas25 pts
005Deal closedDeals Fechados100 pts
You don’t need to use all 999 codes. Most teams use 5-20 codes. Start small and add more as needed.

Idempotency

The external_id field guarantees idempotency. If you send the same activity twice with the same external_id, the second request reports it as a duplicate — it is not processed again. First call — activity is processed:
curl -X POST https://api.play2sell.com/functions/v1/default-integration \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "sync_activities",
    "activities": [{
      "activity_code": "001",
      "external_id": "crm-call-5001",
      "collaborator_email": "maria@yourcompany.com"
    }]
  }'
{ "data": { "processed": 1, "skipped": 0, "duplicates": 0, "errors": [], "total": 1 } }
Second call (same external_id) — safely deduplicated:
{ "data": { "processed": 0, "skipped": 0, "duplicates": 1, "errors": [], "total": 1 } }
Use your CRM’s event ID as the external_id. This ensures that even if your sync job runs twice (e.g., after a crash and retry), activities are never double-counted.

Error Handling

Error Response Format

All errors follow a consistent structure:
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description",
    "details": []
  }
}

Error Codes Reference

Invalid body, missing fields, or bad activity code. Fix the request payload — check the details array for field-level specifics.
API key is missing, invalid, or expired. Check your Authorization header and verify the key in the Dashboard. Generate a new one if expired.
API key is valid but lacks the default:sync scope. Edit the key in Dashboard and add the required scope.
Used GET, PUT, etc. instead of POST. Change to POST.
Too many requests this hour. Wait retry_after seconds, then retry. Consider reducing your sync frequency.
Internal server error. Retry with exponential backoff (2s, 4s, 8s). Contact support if persistent.

Example: Validation error with details

# Sending an invalid activity_code and a bad email
curl -X POST https://api.play2sell.com/functions/v1/default-integration \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "sync_activities",
    "activities": [
      { "activity_code": "abc", "external_id": "e1", "collaborator_email": "maria@co.com" },
      { "activity_code": "001", "external_id": "e2", "collaborator_email": "not-an-email" },
      { "activity_code": "001", "external_id": "e3", "collaborator_email": "joao@co.com" }
    ]
  }'
Response (400):
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid activities payload",
    "details": [
      { "index": 0, "field": "activity_code", "message": "Required 3-digit string (001-999), e.g. \"001\"" },
      { "index": 1, "field": "collaborator_email", "message": "Required valid email" }
    ]
  }
}
The index field tells you which item in the array has the problem. Item at index 2 (joao) was valid — only the invalid items are listed.

Example: Rate limit exceeded

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded",
    "retry_after": 1847
  }
}
Wait 1847 seconds (~31 minutes) before retrying. The rate limit resets every hour.

Example: Invalid API key

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or expired API key"
  }
}

Complete Code Examples

# Step 1: Set your API key
export SALESOS_KEY="sk_live_YOUR_API_KEY"
export SALESOS_URL="https://api.play2sell.com/functions/v1/default-integration"

# Step 2: Register your team (run once, or nightly to keep in sync)
curl -s -X POST "$SALESOS_URL" \
  -H "Authorization: Bearer $SALESOS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "sync_collaborators",
    "collaborators": [
      { "external_id": "emp-101", "name": "Maria Santos", "email": "maria@co.com", "team": "SP" },
      { "external_id": "emp-102", "name": "Joao Silva", "email": "joao@co.com", "team": "SP" },
      { "external_id": "emp-103", "name": "Ana Oliveira", "email": "ana@co.com", "team": "RJ" }
    ]
  }' | jq .

# Step 3: Send today's activities
curl -s -X POST "$SALESOS_URL" \
  -H "Authorization: Bearer $SALESOS_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "sync_activities",
    "activities": [
      { "activity_code": "001", "external_id": "call-1001", "collaborator_email": "maria@co.com", "data": {"client": "Acme"} },
      { "activity_code": "001", "external_id": "call-1002", "collaborator_email": "maria@co.com", "data": {"client": "Beta"} },
      { "activity_code": "002", "external_id": "meet-2001", "collaborator_email": "joao@co.com", "data": {"client": "Gamma", "type": "video"} },
      { "activity_code": "003", "external_id": "prop-3001", "collaborator_email": "joao@co.com", "data": {"value": 50000} },
      { "activity_code": "001", "external_id": "call-1003", "collaborator_email": "ana@co.com", "data": {"client": "Delta"} }
    ]
  }' | jq .

Best Practices

Sync Strategy

  • Collaborators: Sync your full team nightly. The API is idempotent — existing users are updated, not duplicated.
  • Activities: Sync every 5-15 minutes, or in real-time via webhooks from your CRM. Always use your CRM’s event ID as external_id.
  • Batch size: Send up to 1000 activities per request. For large volumes, split into sequential batches.

Choosing external_id

The external_id is your deduplication key. Pick something stable and unique from your source system:
SourceGood external_idBad external_id
CRMcrm-event-12345random-uuid-each-time
Databasedb-row-id-789timestamp-only
Webhookwebhook-delivery-abcuser-email (not unique)

Handling failures

  • 400 errors: Fix the data and resend. Check the details array for field-level errors.
  • 429 errors: Wait retry_after seconds, then retry. Consider reducing your sync frequency.
  • 500 errors: Retry with exponential backoff (2s, 4s, 8s). Contact support if it persists.
  • Network errors: Retry safely — idempotency via external_id prevents duplicates.

Rate Limits

Each API key has a configurable rate limit (default: 1000 requests per hour). The counter resets every hour.
LimitValue
Default requests per hour1000
Max collaborators per request500
Max activities per request1000
Timeout per request120 seconds

Security

  • API keys are hashed with bcrypt — never stored in plaintext
  • Each key is scoped to a single tenant — no cross-tenant access
  • IP allowlists can be configured per key
  • All requests are logged for audit purposes
  • Keys can be revoked instantly from the Dashboard
Never expose your API key in client-side code (JavaScript running in the browser). The API should only be called from your backend server.

Next Steps

Authentication

Learn how to create and manage API Keys

Support

Need help? Contact our support team