A modern, multi-tenant Customer Relationship Management platform built to manage contacts, companies, sales pipelines, opportunities, tags, lists, and webhook integrations all in one place.
Navigate to:
https://coreswiftcrm.com
Enter your email and password.
On success, you receive a JWT access token and refresh token. The app stores these for all API calls. You'll be redirected to the Dashboard.
All endpoints sit under /api/:
auth/login, auth/register, auth/forgot-password, auth/reset-passwordAuthorization: Bearer <token>At-a-glance CRM metrics for your own account, fetched via GET /api/dashboard/stats. Every counter is scoped to the account in the token.
GET /api/dashboard/stats
Authorization: Bearer <token>
Response:
{
"total_contacts": 42,
"total_companies": 15,
"total_opportunities": 28,
"total_revenue": 5000.00
}
total_contacts, total_companies and total_opportunities are row counts for your account. total_revenue is money, not a count: it is the sum of opportunities.value over the opportunities you have marked won, and 0 when you have none. These four are the whole response — no other key is returned.
Below the stats cards, each active pipeline shows opportunity counts per stage, giving a visual funnel of where deals are concentrated. Default pipelines are highlighted.
Search all entities at once:
GET /api/dashboard/search/query?q=david
Matches across contacts (first name, last name, full name, email, phone), companies (name/email), and opportunities (name). Returns top 10 per type, and only from your own account.
GET /api/dashboard/search/query?q=david
Authorization: Bearer ***
Response:
{
"query": "david",
"total_contacts": 42,
"total_companies": 15,
"contacts": [
{ "id": "1f0c...", "first_name": "David", "last_name": "Giraudy", "email": "david@example.com", "phone": "+13055550100" }
],
"companies": [
{ "id": "9a24...", "name": "Acme Corp", "email": "info@acme.example" }
],
"opportunities": [
{ "id": "77c1...", "name": "David renewal", "value": 12000.00, "is_won": false }
]
}
total_contacts and total_companies are your own row totals — the same numbers as GET /api/dashboard/stats — and not the number of matches; the matches are the three arrays. A request with no q (or a blank one) returns those totals with three empty arrays, and so does a query that matches nothing.
| Field | Type | Description |
|---|---|---|
first_name | String | Required |
last_name | String | Required |
email | String (opt) | Email address |
phone | String (opt) | Phone number |
company | String (opt) | Free-text company |
job_title | String (opt) | Job title |
city/state/country | String (opt) | Location |
source | String (opt) | Acquisition source |
score | Integer | Auto-calculated lead score |
metadata | JSON (opt) | Custom data |
Click Contacts in the sidebar.
A form modal opens.
First / last name required. Email or phone recommended.
Click Save in the dialog.
POST /api/contacts
Authorization: Bearer <token>
Content-Type: application/json
{
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"phone": "+1234567890",
"company": "Acme Corp",
"source": "Website"
}
PATCH /api/contacts/:idDELETE /api/contacts/:id) — a contact row has no delete controlGET /api/contacts/search?q=john (name/email/phone)Click Tags on the contact's row.
Existing tags are shown as colored pills, each with an ✕ to unassign.
Pick one of the remaining tags listed below the pills.
Uses POST /api/tags/assign. Remove via DELETE /api/tags/assign/:id.
Score rules fire automatically:
| Rule | Points | Direction |
|---|---|---|
| Email Opened | +5 | Add |
| Email Clicked | +15 | Add |
| Email Replied | +25 | Add |
| Booked a Call | +50 | Add |
| Proposal Requested | +75 | Add |
| No Activity 30 Days | -20 | Subtract |
| Unsubscribed | -100 | Subtract |
Over time the same person can end up in your workspace more than once — imported from a spreadsheet on Monday, added by hand on Friday. The Duplicates screen (in the sidebar, directly under Contacts) shows you every set of records that look like the same person so you can decide what to keep.
It compares two things:
(561) 555-0143 and 5615550143 count as the same number.Email addresses are deliberately not compared: a workspace can never hold two active contacts with the same email address, so a report on email could only ever come back empty.
Nothing is merged automatically. Each row has an Edit button that opens that contact in the normal editor, so you decide which record survives and what it says. If a group names a record you cannot see in the list above it, the record is still there — the name lookup stops after 500 contacts, and the screen says so rather than pretending the contact is missing.
If you see "Account matching is not available on your current plan", the report is switched on for paid plans only. The screen tells you that instead of showing an empty list, because "you have no duplicates" and "you are not allowed to look" are different answers.
GET /api/account-match/duplicates?limit=25
Authorization: Bearer ***
{
"groups": [
{ "kind": "phone", "key": "5615550143", "count": 2,
"contact_ids": ["6b5f8ccc-…", "cd67aebd-…"] }
],
"total_groups": 1,
"contacts_in_groups": 2
}
| Field | Type | Description |
|---|---|---|
name | String | Required |
website | String (opt) | Company URL |
phone | String (opt) | Main phone |
email | String (opt) | Contact email |
industry | String (opt) | e.g. Technology, Healthcare |
size | String (opt) | 1-10, 11-50, 51-200, 201-1000, 1000+ |
city/state/country | String (opt) | HQ location |
description | Text (opt) | Free text |
Companies has no sidebar entry — press g then o to open it.
Name required. Optionally set industry/size.
POST /api/companies
Content-Type: application/json
{ "name": "Acme Corp", "industry": "Technology", "size": "51-200" }Companies have no detail page; a contact carries a free-text company field and the Companies list shows a per-company contact count.
Soft delete (is_active = false).
Link a company to an opportunity via company_id to track all deals per organization.
industry and size for segmentation and reporting.A Pipeline is a sales process composed of sequential Stages. Opportunities move through these stages toward close.
| Stage | Color | Order | Probability |
|---|---|---|---|
| New Lead | Gray | 0 | 5% |
| Contacted | Blue | 1 | 10% |
| Qualified | Purple | 2 | 25% |
| Appointment Set | Amber | 3 | 40% |
| Proposal Sent | Green | 4 | 60% |
| Won | Dark Green | 5 | 100% |
| Lost | Red | 6 | 0% |
| Stage | Color | Order | Probability |
|---|---|---|---|
| New Client | Gray | 0 | 10% |
| Documents Received | Blue | 1 | 25% |
| Setup In Progress | Purple | 2 | 50% |
| Active | Green | 3 | 100% |
| Retention | Dark Green | 4 | 100% |
Click Pipelines & Deals under Sales in the sidebar.
Enter a name and optional description.
Each stage needs name, color, sort order and probability.
The pipeline appears on the Pipelines & Deals screen and counts towards the dashboard Pipelines tile.
POST /api/pipelines
Authorization: Bearer <token>
{ "name": "Support Pipeline", "description": "Support lifecycle", "is_default": false }
Each stage supports:
#3B82F6)An Opportunity (deal/tracker) represents a potential sale or business outcome progressing through a pipeline. Each opportunity is tied to a specific pipeline and stage.
| Field | Type | Description |
|---|---|---|
name | String | Required. Deal name |
pipeline_id | UUID | Required. Which pipeline it belongs to |
stage_id | UUID | Required. Current stage within the pipeline |
value | Float (opt) | Deal value / amount |
currency | String (opt) | e.g. USD, EUR |
probability | Integer (opt) | Override win probability |
expected_close_date | Date (opt) | Expected close date |
contact_id | UUID (opt) | Associated contact |
company_id | UUID (opt) | Associated company |
source | String (opt) | e.g. Website, Referral |
notes | Text (opt) | Internal notes |
is_won | Boolean | Marked as won |
is_lost | Boolean | Marked as lost |
lost_reason | String (opt) | Why the deal was lost |
Open the pipeline where you want to add a deal.
The New Deal dialog opens.
Name is required. Set value, stage, expected close date. Optionally link a contact and/or company.
The opportunity appears in the correct stage column.
POST /api/pipelines/:pipeline_id/opportunities
Authorization: Bearer ***
{
"stage_id": "uuid-of-stage",
"name": "Enterprise Deal - Acme Corp",
"value": 50000.0,
"currency": "USD",
"probability": 60,
"expected_close_date": "2026-08-15",
"contact_id": "uuid-of-contact",
"company_id": "uuid-of-company",
"source": "Referral",
"notes": "Initial call went well"
}
To advance an opportunity to the next stage:
Both actions use:
PATCH /api/pipelines/:pipeline_id/stages/:stage_id/move/:opportunity_id
Authorization: Bearer ***
{ "stage_id": "uuid-of-next-stage" }
Value, probability, expected close date and notes are updated with PATCH /api/pipelines/:pipeline_id/opportunities/:id.
When a deal reaches a Won or Lost stage, CoreSwift automatically sets the corresponding boolean flags:
is_won = true adds value to the "Won Deal Value" dashboard statis_lost = true optionally record a lost_reason for analysisThe dashboard carries three of these tiles, each read from GET /api/dashboard/stats:
Per-stage counts are not shown anywhere.
The sidebar's Sales group holds one screen, Pipelines & Deals (each pipeline's cards are its deals/opportunities). Contacts is a top-level sidebar item of its own, and Companies has no sidebar entry — open it with g then o. The group is collapsed by default — click Sales to expand it.
Contact management with search (/api/contacts, GET /api/contacts/search), pagination (50 rows per page by default) and add/edit dialogs. A contact has no status field and the list has no status column.
GET /api/contacts POST /api/contacts PATCH /api/contacts/:id
Track organizations with name, domain, industry, and contact count. Searchable and paginated.
GET /api/companies POST /api/companies PATCH /api/companies/:id
Manage sales pipelines with custom stages. Track deals through pipeline stages with value, probability, and stage tracking.
GET /api/pipelines POST /api/pipelines GET /api/pipelines/:pipeline_id/opportunities POST /api/pipelines/:pipeline_id/opportunities
Contains Campaigns, Lists and Analytics. Expand it by clicking Marketing in the sidebar.
Create and manage marketing campaigns. Each campaign has a type (email, SMS, multi-channel), status (draft/active), and sent count. Activate campaigns from the list view.
GET /api/campaigns POST /api/campaigns PATCH /api/campaigns/:id POST /api/campaigns/:id/activate POST /api/campaigns/:id/pause
Build automated email sequences with multiple steps. Each sequence can have triggers, enrollments, and step-based delays. There is no separate Email Sequences screen — sequences are campaign records, managed under Marketing > Campaigns.
GET /api/campaigns GET /api/campaigns/:id (includes steps) GET /api/campaigns/:id/enrollments (view enrolled contacts)
Create trigger-based automation rules. Configure triggers (tag added, score changes, list membership changes) and actions (add/remove tag, move stage, add/remove from list). Each rule has a trigger config and action config stored as JSON.
Available triggers: tag_added, score_change, list_added, deal_stage
Available actions: add_tag, remove_tag, move_stage, add_to_list, remove_from_list
GET /api/automation/rules POST /api/automation/rules PATCH /api/automation/rules/:id DELETE /api/automation/rules/:id
{"tag_id": "uuid"} for tag-based triggers, {"category": "overall", "min_score": 70} for score-based.Define checklist templates with stages for onboarding and process management. Each stage has an order, title, optional message template reference, and delay in hours. View instances and track progress.
GET /api/checklists/templates POST /api/checklists/templates PATCH /api/checklists/templates/:id DELETE /api/checklists/templates/:id GET /api/checklists/instances
There is no Operations group in the sidebar. The recorded event feed is API-only; portfolio companies are managed in the platform console under Portfolio Companies; lists live under Marketing > Lists.
Logged system events — each with its type, description, related entity and date — are read through the API. There is no Events screen in the console.
GET /api/events
View all portfolio companies across your user ecosystem. Searchable by name, slug, or domain. Shows status, plan assignment, and active/inactive state.
GET /api/portfolio
If the CoreSwift team set your company up, your workspace and your owner login were created together, and the login details were emailed to the address on the company record. Sign in with that password, then change it in Settings → Change password. If no email arrived, use Forgot password? on the sign-in page — that sends a fresh link to the same address.
Telnyx call records — caller, called number, duration, disposition, cost and timestamp — are stored for billing and audit. There is no Call Logs screen in the CRM and no endpoint that lists them, so they are not shown in the app today.
Create and manage contact lists. Each list shows member count and can be used for campaign targeting. View members inline from the list detail.
GET /api/lists POST /api/lists PATCH /api/lists/:id DELETE /api/lists/:id GET /api/lists/:id/members POST /api/lists/:id/members
CoreSwift CRM includes a complete customer support ticketing system. Replace Freshdesk, Zendesk, or your overloaded email inbox. Your customers submit requests. Your team manages them. Nothing leaves the platform.
No API keys. No chatbot plugins. No external subscriptions. It just works.
Tickets can arrive through multiple channels — all tracked by source so you know where every request came from:
| Channel | Source Code | How |
|---|---|---|
| 👤 Manual | manual | Your team creates tickets in the admin Support panel |
| 🌐 Contact Form | form | Drop our embed widget on any website — visitors submit tickets without logging in. Anyone who writes from the contact page is handed a reference, and can come back to that page with the reference and their email address to read your team's answer — no sign-in needed. |
email | Forward your support@ inbox to our parser — emails become tickets automatically | |
| 🤖 Chatbot | agent | Connect any chatbot or AI agent via our public API — tickets created programmatically |
None of these channels is privileged. A ticket created by your chatbot, your contact form, or your staff member is treated identically. No special routing, no hidden costs.
Add a floating support button to any website with one script tag:
<script src="https://coreswiftcrm.com/s/YOUR_TENANT_ID/widget.js"></script>
That gives you:
Your team manages everything from 🎫 Support in the sidebar:
Your end customers open 💬 My Support and see only their own requests. The portal lives at
/s/<your-tenant-id>/support, and your team can open it from Support Tickets → 💬 My Support.
There is no password to issue and no email needed to sign in: the customer enters their email address plus the reference of a request they already have (the first 8 characters of the ticket id, shown on the ticket in your console). That pair proves the address, and the server hands back an access grant that never travels in a URL.
This is their personal help desk — not your admin panel. They manage their own support experience.
For programmatic access — connecting chatbots, Zapier, custom apps:
POST /s/:tenant_id/ticket GET /s/:tenant_id/widget.js POST /api/public/contact
Set source: "agent" when creating tickets from chatbots or AI systems. The public endpoints require no authentication — designed for third-party consumption.
Free tier: The help desk is not included — the ticket queue and the admin support panel refuse a Free workspace with 402 Payment Required (GET /api/tickets: "Support tickets is not available on your current plan"), so Support Tickets → 💬 My Support is unavailable too.
Paid tier: Support tickets (embed widget + admin support panel + customer portal) + custom domain (use your own support.yourdomain.com) + email-to-ticket parsing + priority support.
The public intake arms are not plan-gated, by decision: the embed widget script at /s/<tenant-id>/widget.js and the customer portal page are served on every plan, and a visitor's request is accepted and stored — a customer's request is never refused because of your plan, so nothing is lost either way. What a Free workspace gets instead of silence: the Dashboard shows a Customer requests you cannot open notice naming how many requests are waiting, the workspace dropdown flags them (⚠ cannot open), and GET /api/auth/me/usage carries the three fields the notice is built from (tickets, tickets_readable, support_intake_unreadable). Stored requests appear in Support Tickets the moment the plan includes it.
Chatbot integrations are API-only on all tiers — you connect your own chatbot, we handle the ticket routing.
The Automation group (sidebar icon ⚡) contains Rules, Tags and Lead Scoring. The strategy-engine screens this section used to name are not in the console; each card below says what is API-only instead.
There is no Agency Sequences screen. Rules are read through GET /api/automation/rules; the Rules screen under Automation is the rule UI.
GET /api/automation/rules
There is no Directory Onboarding screen. Checklist instances (entity, stage, progress, start date) are rendered on the Checklists screen under Workflow and read through GET /api/checklists/instances.
GET /api/checklists/instances
Account health is computed by the API: POST /api/monitoring/account-health/check runs an analysis and GET /api/monitoring/health reads it. In the console, the platform-admin Monitoring screen renders those endpoints; there is no "Run Health Check" button in the tenant workspace.
POST /api/monitoring/account-health/check GET /api/monitoring/health
Create reusable message templates for email and SMS communications. Each template has a name, channel, subject and body. Manage them in the console under Communication > Email Templates.
GET /api/comms/templates POST /api/comms/templates PATCH /api/comms/templates/:id DELETE /api/comms/templates/:id
Contact enrollments are read through GET /api/campaigns/:id/enrollments. There is no Nurture Campaigns screen; the campaigns themselves are managed under Marketing > Campaigns.
GET /api/campaigns/:id/enrollments
General details, SEO/meta (title, description, keywords, Open Graph) and branding colours are edited in the
platform console under Admin Settings > Configuration ("Site Configuration"), which reads and
writes GET/PUT /api/admin/site — server-side, not in the browser. The tenant workspace has no
Settings page of its own.
Entries below the ── ADMIN ── divider appear only for a user the server marks as a platform
admin (users.is_platform_admin, carried in the session as platform_admin). A tenant
owner/admin role is not platform authority. The entries are:
There is no Impersonate button in the console: the API can start and stop impersonation, but no screen calls it.
People live on the Users screen — every workspace's users in one list with name, email, role and joined date.
GET /api/admin/users GET /api/admin/tenants
Modules & Plans is where each plan's modules and individual features are switched on, and where a single workspace can be granted or denied one feature without touching its plan. Enter a workspace id to load the features it effectively gets, then tick one to override it.
GET /api/admin/modules POST /api/admin/plans/:slug/modules POST /api/admin/plans/:slug/features GET /api/admin/tenants/:id/entitlements POST /api/admin/tenants/:id/overrides
Plans control what features users can access. Each plan has:
Available features are not a fixed list: they are the boolean flags in the module registry (modules / module_features) — the same switches the Modules & Plans panel renders. See Plans & Feature Access for the flags live today.
GET /api/billing/plans POST /api/billing/plans PATCH /api/billing/plans/:id DELETE /api/billing/plans/:id GET /api/billing/features
Telnyx provider settings are read and written through the API: GET /api/telnyx/config,
PUT /api/telnyx/config, and GET /api/telnyx/available for the phone numbers on
the account. The console's Telnyx Admin entry (Communication Admin group) is a placeholder
screen today, so there is no form to paste the key into.
GET /api/telnyx/config PUT /api/telnyx/config GET /api/telnyx/available
The platform-admin API can start and stop impersonation, but the admin console has no Impersonate button yet — no screen calls it.
CoreSwift CRM offers free access with a limited feature set. When your plan does not cover something, the console shows an upgrade banner whose Upgrade Now button opens the plan comparison. Each tier that is not your current one carries a Request this plan button: pressing it sends the request to our team (nothing is charged, and no plan changes by itself) and the request names your workspace and the address to answer. Putting the workspace on a plan is therefore still an operator action — self-service checkout is not open on this install — but asking is now one press inside the app instead of a support email you had to compose yourself. Feature availability is determined by your plan's feature flags. A tier that carries a payment page also shows Open payment page: it opens that page, and pressing it files the same request first, so our team knows which workspace is paying (nothing is charged by this app).
CoreSwift CRM supports both incoming and outgoing webhooks for integration with satellite apps and external systems.
Other apps can send events to CoreSwift using authenticated webhook endpoints. The URL carries only the key prefix, which is a public index; the credential is the full key in the X-Satellite-Key header, and its SHA-256 digest must equal the digest stored for that prefix. A missing header or a wrong key is refused with 401.
POST /inbound/:key_prefix/:event_type
X-Satellite-Key: <full-api-key>
Content-Type: application/json
{ "payload": "data" }
Or the v2 endpoint:
POST /inbound/v2/:key_prefix/:event_type X-Satellite-Key: <full-api-key>
And the v3 contact-sync endpoint — POST /inbound/v3/:key_prefix/contact-sync (also survey-complete and contact-upsert) — with the same header.
/api/, and the public web vhosts proxy only /api/, /s/ and /track/ to it, so on the shared host a satellite app calls CoreSwift directly rather than through the public site.Supported event types from satellite apps:
| Source App | Event | Action |
|---|---|---|
| FunnelSwift | lead.created / lead.updated | Creates or updates a contact in CoreSwift |
| IncentiveSwift | entry.created | Creates contact from giveaway entry |
| IncentiveSwift | raffle.winner | Records raffle winner |
| IncentiveSwift | loyalty.reward | Records loyalty reward |
| WorkflowSwift | workflow.completed | Logs completed workflow |
| WorkflowSwift | instance.advanced | Logs workflow instance progression |
| MissedCall | call.missed | Creates contact from missed call |
| MissedCall | followup.created | Logs follow-up creation |
You can register endpoints that CoreSwift will POST events to. CoreSwift delivers events to each
endpoint that is active and subscribes to that event type.
A workspace configures its own endpoints, signing secret and event subscriptions in the console
under Settings → Webhooks: that screen lists each endpoint's name, URL, events,
active state, last successful delivery and failure count, and can add, edit, pause, activate and
delete them. Outbound webhooks are sold on Enterprise/Agency — a workspace whose plan lacks the
webhooks module has its requests to the whole surface refused with
HTTP 402 and the reason in the response body. The platform console
(admin.coreswiftcrm.com) has no editor: the endpoints are workspace-scoped, so it only points here.
These same calls remain available directly on the API:
POST /api/integrations/webhooks
Authorization: Bearer ***
{
"name": "Slack Notifier",
"url": "https://hooks.slack.com/services/xxx",
"secret": "your-signing-secret",
"events": ["contact.created", "opportunity.won"],
"retry_count": 3,
"timeout_ms": 5000
}
List configured webhooks:
GET /api/integrations/webhooks
Authorization: Bearer ***
Response: { "webhooks": [...] }
What arrives. Each delivery is a JSON POST:
POST <your url>
X-CoreSwift-Event: contact.created
X-CoreSwift-Delivery: <endpoint id>
X-CoreSwift-Delivery-Attempt: 1
X-CoreSwift-Signature: t=1759512345,v1=<hex>
{
"event": "contact.created",
"tenant_id": "...",
"webhook_id": "...",
"delivered_at": "2026-10-06T16:45:00+00:00",
"data": { ...the event payload... }
}
Verifying the signature. When the endpoint has a signing secret the
X-CoreSwift-Signature header is t=<unix seconds>,v1=<hex>, where
v1 is HMAC-SHA256(secret, "<t>.<raw request body>") — compute the
same value and reject a mismatch. An endpoint saved without a secret is sent unsigned.
Which events, and retries. An endpoint whose events list is empty
receives every event; a non-empty list receives only those event types. An endpoint with
is_active: false receives nothing. A failed delivery (anything other than a 2xx) is
retried retry_count times with a growing backoff, each attempt bounded by
timeout_ms; redirects are never followed. The endpoint's
last_triggered_at shows its last successful delivery and failure_count
how many deliveries have failed since the last success.
For portfolio-level integrations, CoreSwift supports Integration Targets named connections to external systems with webhook URLs and API keys:
POST /api/portfolio/:id/targets
Authorization: Bearer <token>
{
"name": "Mailchimp Sync",
"provider": "mailchimp",
"webhook_url": "https://mailchimp.com/webhook",
"portfolio_company_id": "optional-uuid"
}
Use the simple test endpoint to verify connectivity:
POST /simple-hook
Content-Type: application/json
{ "test": true }
Response: { "ok": true }
Under Settings > Profile you can view and update your profile:
admin (full access) or member (limited)View your account:
GET /api/auth/me Authorization: Bearer <token>
Use the Change Password card — there is no separate password tab.
For security verification.
Must be at least 8 characters.
Your token remains valid. Next login uses the new password.
PUT /api/profile/password
Authorization: Bearer <token>
{ "current_password": "<your-current-password>", "new_password": "NewSecurePass123!" }
POST /api/auth/forgot-password with your email. Check your inbox for a reset link.Your organization has its own profile:
GET /api/account/:id — View details PATCH /api/account/:id — Update name
API keys allow satellite apps to authenticate when sending data to CoreSwift via webhooks. Each key has:
X-Satellite-Key header. The server stores only its SHA-256 digest (key_hash), compares the two in constant time, and can never recover the full key from the row.Satellite keys have no console screen: they are provisioned per tenant by the operator, who stores the prefix and the SHA-256 digest of the full key in the satellite_api_keys table (key_prefix, key_hash, source app, active flag, last used). The keys a workspace mints itself are the Lead-delivery keys (csk_…) in Settings > Integration Center, backed by POST /api/personal-api-keys (list, rotate and revoke alongside).
For agency-level accounts, CoreSwift supports:
GET/POST /api/portfolio)POST /api/admin/portfolio-syncPOST /api/admin/impersonateThere is no preset-theme picker and no Appearance screen in the console today — the workspace ships a single built-in look. (A widget's own form carries an accent colour for that widget.)
There is no reminder control on a deal, no reminder panel on the dashboard and no Overdue count, and no route that lists or creates reminders — so there is nothing to set up here yet.
There is no Forecast screen and no forecast endpoint, so no weighted-pipeline view is shown anywhere. Deal value by stage is visible on Sales > Pipelines & Deals, which lists each pipeline's deals with its won / open / lost counts.
Every stage move is written down as it happens — which stage it moved from and to, and who moved it — so a deal's progression is kept.
The CRM has no Activity Timeline screen, and no endpoint that lists a deal's history, so that history is not visible in the app today. Platform-wide events (type, description, related entity, date) can be read through the API:
GET /api/events
Migrate your existing contacts from any CRM or spreadsheet with a single CSV import:
Two tables export to CSV: your contacts and your deals.
The same header row the export writes can be loaded straight back in, so an export doubles as a backup you can restore:
Each row needs a name. pipeline and stage are used when the file names ones that exist; otherwise the row goes into your first pipeline. A contact is linked only when its email or full name matches an existing contact. A file with no name column is refused by the console instead of importing nothing.
The New Deal dialog links the deal to one of your existing contacts from a dropdown; it has no inline name/email/phone fields, so create the contact first if it is missing.
This pattern is also available in sister apps (FunnelSwift and IncentiveSwift) for seamless lead-to-deal creation.
| Column Header | Maps To |
|---|---|
name | Contact Name (required) |
email | Email Address |
phone | Phone Number |
company | Company Name |
title | Job Title |
notes | Notes / Description |
tags | Comma-separated tag names (auto-created) |
CoreSwift CRM — v0.1.0
Built by SwiftSoftware | coreswiftcrm.com
Customer Relationship Management Platform · API /api/
Your affiliate manager provides a unique tracking link (or you can generate one in the FunnelSwift affiliate portal).
Post the link on your website, in emails, on social media, or wherever your audience is.
When someone clicks your link and later completes a paid purchase, the system tracks the conversion.
Commissions are calculated based on your affiliate rate and credited to your account automatically.
Commissionable products live in the affiliate portal (FunnelSwift → Affiliate Products) and that list is the source of truth — a paid plan of any service can only be promoted once an admin has added it there. Platform-wide entries are visible to every affiliate; a workspace's own products are visible to its affiliates. Measured 2026-09-26, the catalogue holds:
| Scope | Product | Price |
|---|---|---|
| All affiliates | CoreSwift Free | $0 |
| ADASwift Free | $0 | |
| IncentiveSwift Free | $0 | |
| MissedCall Respondr Free | $0 | |
| WorkflowSwift Free | $0 | |
| FunnelSwift workspace | Capture Free | $0 |
| Capture Starter | $9/mo | |
| Kinetic Free | $0 | |
| Kinetic Pro | $9/mo | |
| Suite | $29/mo | |
| Agency / Scale | $79/mo |
The rate earned on a sale is your own affiliate rate — the one on your affiliate account — not a per-product figure. Products, prices and rates are edited on the product record in the portal, so the portal is always the current list.
Attribution lives in FunnelSwift. The trigger is a plan change in this app — there is no checkout session and no payment-provider webhook anywhere in the chain:
When someone arrives through your affiliate link, FunnelSwift keeps them as a lead on your affiliate account. That lead is the attribution — it is permanent, with no cookie to expire or be lost.
When the referred business moves to a paid plan here (the CoreSwift platform admin assigns the plan to the workspace — POST / PATCH /api/billing/subscription are operator-only writes, with no customer self-service plan change), CoreSwift looks up the account owner's email address and posts it to FunnelSwift's internal affiliate endpoint, POST /api/v1/internal/affiliate/upgrade-event, together with the plan name and its price.
FunnelSwift matches that email back to the lead, applies the affiliate's rate to the plan price, and records the commission against the event. A repeat of the same upgrade event is ignored, so nothing is credited twice.
This is fully automatic — nothing extra is needed as long as the customer reaches you through your affiliate link. Two details worth knowing: attribution is matched by the email address the referred business signed up with, and only a move onto a paid plan counts as an upgrade.
Affiliates are managed through the FunnelSwift Admin portal:
To access, log in to FunnelSwift and navigate to Super Admin → Affiliate Admin.
Features are not hard-coded. Each plan carries a set of feature flags, and every flag can be switched on or off per plan by an admin — so a Free tier can be a genuinely stripped-down CRM while Enterprise unlocks everything. A single tenant can also be given a per-tenant override without changing the plan itself.
When a feature is switched off for your plan, its API returns 402 Payment Required with a message naming the feature, and the corresponding area of the app is unavailable until the plan is upgraded. Features you do have are untouched.
| Flag | Feature | Module | Notes |
|---|---|---|---|
campaigns | Campaigns | campaigns | Sequenced email campaigns |
automation | Automations | automation | Trigger/action engine |
checklists | Checklists | checklists | Onboarding/process checklists |
ai_enabled | AI scoring & helpers | ai_enabled | Lead scoring and AI helpers |
tickets | Support tickets | tickets | In-house ticketing + email-to-ticket |
native_apps | Native app connectors | native_apps | FunnelSwift, ADASwift, MissedCall, WorkflowSwift, Multi-Directory |
telnyx | SMS & voice (Telnyx) | telnyx | SMS, number management, call tracking |
round_robin | Round-robin routing | round_robin | Fair lead distribution across a team |
events | Event system | events | Internal event bus |
monitoring | Monitoring & health | monitoring | Account health scoring and thresholds |
bookings | Bookings & scheduling | bookings | Calendar booking pages |
google_calendar | Google Calendar sync | google_calendar | Two-way calendar sync |
api_access | API access | api_access | Per-tenant API keys |
webhooks | Webhooks | webhooks | Outbound webhook delivery |
integrations | Integrations | integrations | n8n and third-party integrations |
provider_keys | Provider keys | provider_keys | Bring-your-own provider credentials |
support_widgets | Support widgets | support_widgets | Embeddable support surfaces |
tracked_links | Tracked links | tracked_links | Click tracking links |
private_email | Private mailbox | private_email | Own domain + mailboxes (also has its own limits) |
email_sending | Email sending | private_email | Whether this plan may SEND mail from its mailboxes (receiving is always allowed) |
portfolio | Portfolio sync | portfolio | Cross-tenant portfolio management |
account_match | Account matching | account_match | |
notifications | Notifications | notifications | |
notifications_in_app | In-app notifications | notifications | |
notifications_email | Email notifications | notifications | |
notifications_sms | SMS notifications | notifications | |
notifications_whatsapp | WhatsApp notifications | notifications |
Source of truth: the module registry in the API's database (modules /
module_features) — the same switches the admin panel renders, served by
GET /api/admin/modules. If a flag is registered there it appears here and in the
admin automatically.
CoreSwift is the CRM hub for the rest of the ecosystem. Each connector is a native integration — no third-party middleware:
Each connector can be connected/disconnected and synced per tenant, and is gated by the
native_apps flag above.
Bring your own domain (Mailgun, SMTP, SES or Postmark), provision mailboxes such as
info@yourcompany.com, send through your provider's DKIM/SPF signing (the provider
signs each message — this app hands it over and never signs it itself), and have inbound replies
create contact activity on the CRM timeline. Domain and mailbox counts are plan limits; provider
credentials are encrypted at rest.
Not in this app: spreadsheet import/export is CSV only (XLS/XLSX are not supported), and there is no newsletter/mailing-list sender — campaigns and private email cover outbound email.
When a lead is tagged in FunnelSwift with one of this app's tags — for example CoreSwift — Free — CoreSwift is told to create a free account for that lead, using the same name and email the lead was captured with. CoreSwift emails the new user their login details automatically, so they can sign in and upgrade here whenever they are ready.
The tagged lead is still recorded here as a contact, so nothing is lost, but no account is created and no login email is sent. Use this if you want to review leads before accounts exist for them.
Operators turn this on or off in the admin console, under Provisioning → Tag Provisioning.
FunnelSwift is the hub where affiliates and leads live. Tagging is how a lead is routed to the software they are interested in; the account is created here, in the destination app, so the lead signs in at the app they actually want and upgrades on this app's own plans.
Settings → Email Branding puts this workspace's own logo, display name and colour on every transactional email it sends — the welcome/credentials mail, password resets and purchase confirmations. The branding is this account's; nothing is shared with another workspace.
alt text so a blocked image still says whose mail it is.#4f46e5.An account with neither a name nor a logo gets exactly the mail it got before this feature existed. Removing the logo keeps the name and the colour.
Sidebar → Workspaces (in the Settings group) lists this account’s workspaces. A workspace is a name — a label you give a part of the business, such as “Miami office” or “Wholesale” — and the one marked Default is the workspace a new session is meant to open.
If your plan states a workspace limit, adding past it says so and points you at the plan screen; nothing is created and nothing is lost.
Sidebar → Team & Invites is how a second person gets a real login INTO this workspace. Only the workspace owner and an admin can see it.
Who is looking after what. The Shared Inbox tab lists every address with an Assigned to column: pick a teammate there to make an inbox theirs, or leave it empty to keep it with the whole workspace. Open any conversation and the same Assigned to choice sits on the message. That one answer is also what the Support Tickets queue shows, so a conversation never ends up with two owners in two different systems.
Removing somebody takes their access away at their next click — the session they already hold stops working — and puts their conversations and inboxes back in the workspace. Nothing is deleted: their mail, the tickets they worked and every message stay exactly where they are. The workspace owner cannot be removed this way.
Seats follow your plan. Each plan states how many people it includes. When every seat is in use, Send the invitation answers with the plan that adds more seats and what it costs — for example, “Your plan includes 1 person, and 1 is already in use. Starter raises that to 3 people for $29/month, and you can invite them right away” — and nothing is charged by itself. If the email carrying the password cannot be sent, the list of people says so next to that person, and they can use Forgot password on the login page to choose their own.
A seat is one person who can sign in to this account, and the owner counts as one. On this plan ladder the number of people is: Free 1, Starter 3, Professional 15, Enterprise 20, Agency 100. Pro (5 seats) and the $1 mailbox line are not on sale — neither appears in Plans & Billing and neither can be bought today — and the $1 mailbox includes no second person. Removing a teammate frees their seat at once, so you can add somebody else the same day.
A link still works too. A teammate who would rather pick their own password can use Create invite link under the form: the link stays valid for 7 days and works once.
Your workspace has a public calendar address from the day it is created: <your-workspace>.coreswiftcrm.com. Anyone you give it to sees your open booking calendars, how many places are left and a form to reserve one — no account, no sign-in. It works immediately; there is nothing to switch on.
Open Calendar in the console and look at the card Your public calendar address:
.coreswiftcrm.com — the name your customers will remember. Letters, numbers and hyphens. If somebody else already has it you are told so and can pick another; system names such as app or admin are kept for the platform.calendar.yourbusiness.com. Type it, press Save domain, then create one CNAME record at your domain provider pointing at the address the panel shows you. Press Check now when the record is in place — the panel says domain verified when the domain really reaches your calendar, or tells you what is still missing..coreswiftcrm.com address keeps working throughout, so you can never lose your calendar by changing your mind.The page is public: it shows only the calendars you have marked active and the places that are still open. A booking someone sends from it appears in Bookings like any other.
CoreSwift does not give out an address on our domain. You keep the domain you already own (for example yourbusiness.com) and your own Mailgun account, and your mail is delivered straight to you. Open the Shared Inbox tab in the console: the setup page is the onboarding, in three plain steps, with a copy button beside every value.
mxa.mailgun.org and mxb.mailgun.org, where mail is delivered; SPF and DKIM — v=spf1 include:mailgun.org ~all plus the DKIM value your own Mailgun mints, which prove the mail is really from you; and one CNAME — inbox → inbox.coreswiftcrm.com, which makes inbox.yourbusiness.com open your mail. Every row carries a copy button, one sentence saying what it does, and a live Active / Waiting / Needs attention badge. Press Check again after adding them.Inboxes follow your plan. Free 1, Starter 3, Professional 10, Enterprise 50, Agency 50 — and the setup page states your own number, read from the plan itself, so it can never promise more or fewer than the app will actually create. When you are already at that number, Create new inbox answers with the plan that unlocks more and what it costs, never a dead button.
One difference on Cloudflare: the mail records must be DNS only (grey cloud) — the orange proxy breaks mail. The inbox CNAME is the one exception: it must be Proxied (orange cloud), because Cloudflare then serves the padlock on your own address.
Logging in at the mail client takes you to a mailbox chooser. The screen is headed Choose a mailbox and lists one card per address you created, each card carrying the address and how many messages it holds; pick one and it opens. Signing in to the CRM lands on the Dashboard instead — open the Shared Inbox tab, which lists the same inboxes, each row with a LOG IN button on the right that opens your mail in a new window — at inbox.yourbusiness.com once that record is live, or at inbox.coreswiftcrm.com until it is — plus a Create new inbox button.
Your 30 free days start at your first verified inbox, not at signup: the days you spend waiting on DNS cost you nothing. During the trial, sending has a fair-use ceiling of 200 messages a day to protect the address’s deliverability.
When the 30 days end the address keeps working and keeps receiving. Nothing is deleted. What stops is sending — the mailbox becomes receive-only and the screen offers two buttons: Keep inbox — $1/mo (the mailbox alone) and Upgrade CRM (the whole platform). Upgrade CRM restores sending immediately. The $1 mailbox line is not on sale yet: pressing it opens its plan with a note that sales are not open and reaches our team, and nothing is charged. Every message you already have stays where it is.
What “receive-only” means in plain words: mail keeps arriving and you keep every message you already have. Only Compose and Reply stop, and both answer with the two buttons above rather than an error. Nothing is deleted at expiry, by pressing the wrong button, or by waiting — there is no deadline after which mail disappears.
One bill, never two. The $1 mailbox line is a standalone door, not an add-on. It is not on sale yet — it is absent from Plans & Billing, and pressing its card answers that sales are not open, without charging anything — so no workspace pays it today. If it does go on sale: moving up to a CRM plan ends the $1 subscription in the same step, so you pay the new plan’s price only — never $1 on top of it — and the new period starts the day you move, so nothing is prorated. Plans & Billing opens with Your bill, which states the one amount you pay, that there is one subscription, and — if your plan changed — the line that ended and the day it ended.
When your plan already includes every inbox you have, Create new inbox answers with the plan that unlocks more and what it costs, never a dead button. The $1 single-inbox line is not on sale yet — its card reaches our team and charges nothing — so move up the CRM plans for more addresses.
You do not have to move your mail to one company. The inbox setup page asks two questions that stand on their own: how should your mail leave (sending) and how should your mail arrive (receiving). Every option in those two lists really works — there is no placeholder in either one.
Mixing them is normal. Sending through Amazon SES while receiving through Mailgun — or sending through Mailgun and receiving through SendGrid — is a supported setup, and it is the one that lets you keep whatever already sends your mail. Changing one never changes the other.
mx.sendgrid.net with priority 10. If your domain is on Cloudflare, leave it DNS only (grey cloud).That address is a password. SendGrid’s Inbound Parse signs nothing, so the address itself is the only thing proving a delivery is genuinely yours: anyone holding it can post mail into your inbox. Keep it out of tickets and screenshots, and never put it on a page.
Your sending records stay yours. When you receive through SendGrid, the SPF and DKIM records remain exactly as your sending service gave them — Amazon SES mints its own, SendGrid mints its own, Mailgun mints its own. The setup page says where they come from instead of inventing them. If the same domain also sends through Mailgun, Mailgun’s own sending records are shown, because they are yours and we can read them.
Receiving can be changed without touching sending. The setup page shows Change how this domain receives under the domain; pick Mailgun, SendGrid or Nowhere. Nothing already received is deleted and your sending keys are untouched. Switching to SendGrid issues that domain’s Inbound Parse address if it did not have one.
Two keys, two different jobs, and neither is a password you use to sign in here. You paste both once, on the inbox setup page; the app stores them sealed (encrypted) and uses them only for your own mail. Both are copied from your own Mailgun account.
key-). One key belongs to the whole Mailgun account, so a second domain on the same account can reuse it.A wrong or rotated key is visible, not silent. Paste a new value at any time on the same screen; the old one stops being used the moment it is saved. If Mailgun stops accepting a key, that domain’s badge reads Needs attention with a sentence saying what Mailgun answered, and the rest of the app keeps working.
What it is. Normally mail is accepted only for an address you created, such as info@yourbusiness.com; a message to nobody@yourbusiness.com is refused at the door. Catch-all accepts every message sent to your domain, even when that address does not exist, and files it in your inbox under Everything else (labelled MISC).
When to use it. Turn it on when people write to addresses you never created on purpose — sales@, billing@, a mistyped inof@, the address printed on an old business card — and you would rather read that mail than have it bounce. Leave it off when you want silence: with catch-all off, mail to an unknown address is refused, which is also how a sender learns they have the wrong address.
How to switch it. Sidebar → Inbox → Email Domains & Sending, find the domain and use its Catch-all column: Enable catch-all turns it on, Disable turns it off. It is set per domain, so two domains on one account can differ.
It follows the plan, not the domain. On this plan ladder catch-all is included from Professional ($79) upward — Professional, Enterprise and Agency — and is not sold on Free or Starter. Pressing the button on a plan that does not include it answers with the plan that unlocks it and what it costs, and nothing changes. If the workspace later moves back to a plan without it, your setting is remembered but mail to an unknown address is refused again; the column then reads off — not on your plan, with a Clear button that forgets the setting.
What catch-all is not. It does not create addresses. A catch-all message is not a mailbox, cannot be signed into, and does not start a trial — it is one bucket inside the inbox you already have.
One logo and one workspace signature, both applied automatically. Open Email Branding in the workspace menu: upload the company logo, then fill in the signature — name, title, company, phone and one short line — and press Save signature. From then on every new message and every reply opens with the logo and the signature already in place under the text you type, so you type only the subject and the message.
You never size the logo. Any PNG, JPEG, GIF or WebP up to 2 MB is accepted and the app resizes it for email, so it looks sharp and the right size in Gmail, Outlook and Apple Mail on a phone as well as on a desktop. The preview on the Email Branding screen shows what your customers will see.
Per address, if you want. In the mail client press Signature and choose, for one address: use the workspace signature, write its own wording (a blank field falls back to the workspace value for that field), or send without a signature — useful for a no-reply address. Every other address keeps following the workspace signature.
Leave it off one message. The compose window shows the signature under the message with an Include the signature on this message box already ticked. Untick it and that one message — or that one reply — goes without the signature. Nothing is saved, so the next message starts signed again.
Changing or deleting the logo never breaks mail you already sent. The logo travels inside each message rather than being fetched from a link, so a message sent last month still shows the logo it was sent with even after the logo is replaced or removed. New messages use the new logo immediately.
Every account has a storage allowance, and it grows with the plan. Free starts at 2 GB; Starter 10 GB, Professional 50 GB, Enterprise 100 GB and Agency 250 GB. (Pro and the $1 mailbox line carry 10 GB and 5 GB in the catalogue but are not on sale, so they are not part of this ladder.) Storage counts the mail you have received and sent, plus the logo and profile pictures you upload.
The meter is on the mail screen all the time, at the bottom of the left-hand column, and it states the real figures — for example “1.5 of 2 GB used (75%)” — never just “storage”.
You are warned three times, and at most once per level per month. At 50%, 75% and 99% full a banner appears above the message list and stays there until space is freed. The account owner also gets one email per level per month, carrying the same numbers, what to do, and a link to the mail screen and the plans. The same level is never emailed twice in one month.
When the reminders arrive. The figures are checked whenever you open your mail, and again in the background every ten minutes, so a mailbox nobody opens is still warned by email. One email per level per month is the most you will ever receive — the same level cannot be mailed twice in one month, however much mail arrives in between.
A full mailbox never loses mail. At 100% plain mail still arrives and is stored, sending still works, and nothing is deleted to make room. The only thing that pauses is adding a new attachment or file (for example uploading a logo): that answers with a sentence naming the figures and the two ways out — delete or archive mail (Trash and Spam count toward the figure until emptied), or upgrade for more storage. Everything works again the moment the figure drops below the allowance.
Time-based retention is only a far backstop. Mail is not removed on a clock: the default backstop is ten years (3,650 days), and the mail screen always states the window that applies to the account, together with the date the oldest message would reach it. Freeing space is always your choice — by deleting or archiving — never an automatic purge to make room.
Every domain on Email Domains & Sending has two buttons beside its verification badge.