Skip to content
All articles
Setup

Developer reference: REST API and MCP

The full Sellio API reference: API-key authentication, field discovery, REST endpoints, error format, rate limit, and the MCP server for AI agents.

Integrate Sellio over HTTP or connect your AI agent via MCP. Either way, it is the same key, the same tenant scope and the same rate limit. Discover any object’s fields and start reading and writing records in minutes.

💡 There is also a public developer portal at www.selliocrm.com/developers with this same reference, the outbound webhooks, the OpenAPI spec to download and a Postman collection.

Authentication

Every request carries an API key in the Authorization: Bearer header. That key identifies the owning tenant, so everything is automatically scoped to it. Never expose the key in the browser; use it server-side only. You generate and revoke keys in Settings → API & developers.

curl -H "Authorization: Bearer sk_live_..." \
  https://www.selliocrm.com/api/v1/objects

Key scopes

When you generate a key in Settings → API & developers you choose its scope. Give each integration only the access it needs. The limits apply the same way in REST and MCP, because both surfaces use the same key and the same control.

  • Read only: the key reads data but cannot create, edit or delete. It blocks POST, PATCH and DELETE in REST and the create_record, update_record and delete_record tools in MCP. Write or delete attempts get 403 with { "error": "errors.apiKeyReadOnly" }.
  • Limited to objects: choose the objects the key may access. Any object outside the list returns 403 with { "error": "errors.apiKeyObjectDenied" }. The object list (GET /api/v1/objects and the list_objects tool) already comes filtered to the scope, and activities (/api/v1/activities) count as the activity object: the key reaches them only if activity is in the scope.
  • Allow deletion: by default a key NEVER deletes, even with write enabled. When generating the key you can turn deletion on to enable DELETE /api/v1/{object}/{id} and the delete_record MCP tool. Without that scope, deletion returns 403 with { "error": "errors.apiKeyDeleteDenied" }. Deletion is soft: records go to the trash and can be restored.

A key with no scope set (the default) has full read and write access to all objects, as before. Keys created before this change keep full access until you generate a new scoped key.

OAuth and connected apps

When a THIRD-PARTY system needs to integrate on behalf of the user (without the customer pasting an admin key), use the OAuth 2.0 Authorization Code flow with PKCE. The user sees a consent screen with the requested scopes, approves, and the app receives an access token that can only do what was consented. Apps are curated: the developer registers and publishes the app to get a client_id and client_secret.

  • Redirect the user to GET /api/oauth/authorize with client_id, redirect_uri (exact match against the app allowlist), scope, state and PKCE (code_challenge with code_challenge_method=S256). PKCE is required.
  • The logged-in user approves on the consent screen and the CRM redirects back with code and the same state.
  • On your server, exchange the code at POST /api/oauth/token (grant_type=authorization_code, with code_verifier, client_id, client_secret and redirect_uri) for an access token (Bearer, about 1 hour) and a refresh token.
  • Call the v1 API and the MCP server with Authorization: Bearer at_..., scoped to the consent. Renew with grant_type=refresh_token; the refresh is rotated (the old one dies on use) and reuse revokes the family. Revoke at POST /api/oauth/revoke.
# 1) Consent (the user approves → redirect_uri?code=...&state=...)
GET https://www.selliocrm.com/api/oauth/authorize?response_type=code&client_id=app_...&redirect_uri=https://your-app/callback&scope=records:read%20activities:read&state=abc&code_challenge=...&code_challenge_method=S256

# 2) Exchange the code for tokens (server-side)
curl -X POST https://www.selliocrm.com/api/oauth/token \
  -d grant_type=authorization_code -d code=<authcode> -d code_verifier=<verifier> \
  -d client_id=app_... -d client_secret=secret_... -d redirect_uri=https://your-app/callback

# → { "access_token": "at_...", "token_type": "Bearer", "expires_in": 3600,
#     "refresh_token": "rt_...", "scope": "records:read activities:read" }

OAuth scopes: records:read and records:write (all objects); records:read:contact and records:write:opportunity (per object); activities:read and activities:write; objects:read (discovery). They map to the same read, write and per-object enforcement as keys.

Embedded widgets (iframe)

Your app can embed its own screen (a widget) on a record detail or on the dashboard. The widget runs in a sandboxed iframe, served from your own origin on the app allowlist, and requests the widgets:embed scope. The tenant admin chooses where each widget appears under Apps → Installed.

The host sends the widget only UI context over postMessage (sellio:widget:context, version 1): recordId, objectApiName, locale, theme and installId. No business data, token or secret travels in that message. To read or write data, the widget uses the OAuth API with its own token (the scopes granted at install). The widget may only reply sellio:widget:ready (to receive the context again) and sellio:widget:resize with { height } (to fit its height, capped); any other message is ignored.

Security: the iframe is sandboxed and cross-origin, so the widget cannot reach the host DOM or cookies. The host validates the exact origin on every postMessage and the embedUrl is always checked against the app allowlist. In your widget, accept messages only when event.origin is the host origin.

window.addEventListener('message', (event) => {
  if (event.origin !== 'https://www.selliocrm.com') return; // host only
  const msg = event.data;
  if (!msg || msg.version !== 1) return;
  if (msg.type === 'sellio:widget:context') {
    const { recordId, objectApiName, locale, theme } = msg.payload;
    // data: use the OAuth API with YOUR token (never comes over postMessage)
  }
});
parent.postMessage({ type: 'sellio:widget:ready', version: 1 }, 'https://www.selliocrm.com');

Field discovery

Before creating or updating a record, discover the object’s fields: what exists, what is required, and which values a select accepts. No guessing, and no need to inspect an existing record.

In REST, you get the object and its fields, with required, options (for select) and targetObject (for lookup):

GET https://www.selliocrm.com/api/v1/objects/contact

# → {
#   "apiName": "contact",
#   "label": "Contact",
#   "fields": [
#     { "apiName": "name",  "label": "Name",   "type": "text",  "required": true },
#     { "apiName": "email", "label": "Email",  "type": "email", "required": false },
#     { "apiName": "source", "label": "Source", "type": "select", "required": true,
#       "options": [ { "value": "site", "label": "Website" },
#                    { "value": "referral", "label": "Referral" } ] },
#     { "apiName": "company", "label": "Company", "type": "lookup",
#       "required": false, "targetObject": "company" }
#   ]
# }

In MCP, the describe_object tool returns the same fields to the agent:

{ "method": "tools/call",
  "params": { "name": "describe_object", "arguments": { "object": "contact" } } }

REST API v1

Records of any object, sending and receiving JSON. {object} is the apiName (for example contact, lead, opportunity) and {id} is the record uuid.

  • GET /api/v1/objects: list the tenant’s objects.
  • GET /api/v1/objects/{object}: object fields (discovery).
  • GET /api/v1/{object}: list and search records.
  • GET /api/v1/{object}/{id}: one record.
  • POST /api/v1/{object}: create a record.
  • POST /api/v1/{object}/bulk: create in bulk (up to 500; partial success per item).
  • PATCH /api/v1/{object}/{id}: update fields (partial).
  • PATCH /api/v1/{object}/bulk: update in bulk ({ updates: [{ id, ...fields }] }).
  • DELETE /api/v1/{object}/{id}: delete (soft, goes to the trash; requires the delete scope on the key).

List parameters (GET): limit (1 to 500, default 50), offset (pagination offset, default 0; the total without pagination is in the total field), search (search by name, accent and case insensitive), order (field with :asc or :desc to sort) and filter. Rich filters: repeat filter=field:operator:value to combine several with logical AND (there is no OR; up to 12 per call). Operators: eq, neq, contains, gt, lt, gte, lte, empty, not_empty (empty and not_empty take no value). The old form filter=field:value (no operator) still means exact equality.

# List (limit, offset, search, filter, order)
curl -H "Authorization: Bearer sk_live_..." \
  "https://www.selliocrm.com/api/v1/opportunity?limit=20&order=updated_at:desc&filter=stage:won"

# Rich filters (logical AND: amount >= 1000 AND stage = won)
curl -H "Authorization: Bearer sk_live_..." \
  "https://www.selliocrm.com/api/v1/opportunity?filter=amount:gte:1000&filter=stage:eq:won"

# One record
curl -H "Authorization: Bearer sk_live_..." \
  https://www.selliocrm.com/api/v1/contact/8f3c...

# Create
curl -X POST -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Ana Souza","email":"ana@acme.com","source":"site"}' \
  https://www.selliocrm.com/api/v1/contact

# Bulk create (partial success per item)
curl -X POST -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"records":[{"name":"Ana"},{"name":"Bruno"}]}' \
  https://www.selliocrm.com/api/v1/contact/bulk
# → { "results": [ { "index": 0, "ok": true, "id": "..." } ], "created": 1, "failed": 0 }

# Update (partial)
curl -X PATCH -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"source":"referral"}' \
  https://www.selliocrm.com/api/v1/contact/8f3c...

# Delete (soft, goes to the trash; requires the delete scope on the key)
curl -X DELETE -H "Authorization: Bearer sk_live_..." \
  https://www.selliocrm.com/api/v1/contact/8f3c...

Error format

Errors return JSON in the form { "error": "message" } with the matching HTTP status:

{ "error": "required field: source" }   # HTTP 400
  • 200 and 201: success (201 on create).
  • 400: validation or business rule.
  • 401: missing or invalid key.
  • 403: no permission (RBAC) for the object, or blocked by the key scope (read only, object outside the scope, or deletion not enabled).
  • 404: object or record not found (includes DELETE/PATCH of a nonexistent id).
  • 429: rate limit exceeded (see the Retry-After header).

MCP server (AI agents)

Sellio exposes a native MCP (Model Context Protocol) server so your AI agent can read and write the CRM safely. It is the same API key, the same rate limit, the same validations and the same RBAC. The server URL is https://www.selliocrm.com/api/mcp.

  • list_objects: list the tenant’s objects.
  • describe_object: an object’s fields (required, types, options).
  • list_records: list and search records.
  • get_record: one record by id.
  • create_record: create a record.
  • update_record: update fields.
  • delete_record: delete (soft, goes to the trash); requires the delete scope on the key.

Clients that support remote MCP over HTTP use the URL with the Authorization: Bearer header. On clients without native remote HTTP, use the mcp-remote bridge:

{
  "mcpServers": {
    "sellio-crm": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://www.selliocrm.com/api/mcp",
        "--header", "Authorization: Bearer sk_live_..."
      ]
    }
  }
}

Rate limit

Each key has a per-minute limit (default 120, configurable). Responses include the X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers. On excess, the API returns 429 with Retry-After.

Object model

Each tenant has standard objects (Contact, Company, Lead, Opportunity and others) and custom no-code objects. Discover them all with GET /api/v1/objects and each one’s fields with GET /api/v1/objects/{object} or with describe_object. The schema is always your tenant’s.

💡 Generate your key in Settings → API & developers and make your first call in minutes. Deleting records is available through the REST API and MCP, but it is a soft delete (goes to the trash, reversible) and only works with keys that have the delete scope enabled, off by default.

Open this article inside the system

Read it and want to see it working?

The account is free and the whole manual is available inside the system, with an assistant that answers from this very content.

Create free account
Developer reference: REST API and MCP · Sellio