Skip to content
Odysı
See if we fit
← Odysi Suite
Guide Developers · Beta

Odysi Suite for developers: REST, MCP, OAuth and webhooks

Odysi CRM is a multi-tenant CRM in which every attribute value is a time interval. Odysi holds and shows the data; your own code and your own AI (Claude, ChatGPT or any MCP client) do the work, through a REST API and an MCP server. This page is what you need to read and write that data, connect your tools and agents to it, and react when it changes. Everything described here is shipped and in beta; what is not built yet has its own section.

T
Updated 17 October 2026
Topic Odysi Suite
Read 15 min
No. 01

Two ways in, one workspace

REST at https://crm.odysi.studio/v2 is for code: scripts, services, imports and integrations. MCP at https://crm.odysi.studio/mcp is for AI agents: Claude, ChatGPT, Claude Code or an agent you build. Both take the same credentials, an API key or an OAuth token, both are bound to one workspace, and both run with the permissions of the person behind the credential. Webhooks go the other way: Odysi tells your code what changed.

Odysi CRM makes no generative AI calls of its own. The writing and deciding is done by your code, or by your AI through the connector; the CRM uses AI models only to suggest how CSV columns map to fields and to flag possible duplicates. Odysi Scribe uses AI models to transcribe calls and polish dictation. Odysi Suite is free during the beta and is for business use.

Fig. 1 · From your code or your AI to the workspace, and back
Your code and your AI

A script, a service, Claude, ChatGPT or any MCP client.

REST and MCP

An API key or an OAuth token, bound to one workspace, with the permissions of the person behind it.

Your workspace

Records, lists, notes, meetings and calls. Webhooks (dashed) tell your code what changed.

No. 02

The model: objects, records and values that keep their history

A workspace holds objects (record types). Each object has typed attributes, and records are its rows. Every new workspace has the standard objects companies, people, deals, users and workspaces, and their slugs are the same in every workspace, so an integration can hardcode them. Custom objects and attributes are data, not code: adding one runs no migration. Every query is scoped to your workspace by construction.

  • Ids. A record’s id starts with its workspace: { workspace_id, object_id, record_id }. Every path accepts a UUID or the api_slug, and a renamed slug keeps working for 90 days.
  • Values are intervals. Every value carries active_from, active_until (null while it is current) and created_by_actor. An update closes the old interval and opens a new one; nothing is overwritten. Read the history with ?show_historic=true on /v2/objects/{object}/records/{id}/attributes/{attribute}/values, and ask what a value was at any instant with $as_of in a query.
  • Every change has an author. An actor is a workspace-member, an api-token (with the key’s name and the member it acts for) or system, so history shows a change by an agent as made on behalf of the person.
  • Lists. A list is a process, like a pipeline or a campaign. A record can sit in many lists, and each entry has its own list attributes. Entries have the same query, upsert and history endpoints as records.
  • Writes follow the verb. On a multiselect attribute, PUT on a record replaces the active set and PATCH adds to it.
  • Nothing disappears by accident. Archiving is reversible. Deleting is soft, and an admin can restore within 30 days. A merge gives the record a new id; a GET on an old id returns the merged record with redirected_from, and a merge can be undone for 30 days.
  • The rest is first-class too. Notes, tasks, comments, meetings, call recordings, files, an email log and reports, through REST, MCP or both. The interaction attributes (first, last and next interaction) are computed from meetings and notes and are read-only; formula attributes are computed when you write.

There are 18 attribute types:

  • text
  • number
  • checkbox
  • currency
  • date
  • timestamp
  • select
  • status
  • record-reference
  • actor-reference
  • domain
  • email-address
  • phone-number
  • personal-name
  • location
  • rating
  • interaction
  • formula

A write accepts a bare value, an array of values or explicit objects: "funding_raised": 1200000.5 reads back as a currency value of "1200000.50" in USD, and "foundation_date": "2020-06" as "2020-06-01". Timestamps on the wire are ISO 8601 in UTC with nanosecond precision; dates are YYYY-MM-DD.

Fig. 2 · One attribute, three values over time
Closed intervals

Each old value keeps its active_from and active_until: the history.

$as_of

A query reads the value as it stood at one instant (the teal line).

The current value

Its active_until is null until the next change closes it.

No. 03

Authentication: API keys and OAuth 2.1

Every request carries Authorization: Bearer <token>. Tokens are easy to tell apart: API keys start with odysi_sk_, OAuth access tokens with odysi_at_ and refresh tokens with odysi_rt_. GET /v2/self tells you who a token is: active, scope, client_id, exp (null for an API key), iat, authorized_by_workspace_member_id and the workspace’s id, name and slug.

API keys, for your own code

  • Admins create them in Settings › Connected apps › Advanced › API keys, or with POST /v2/api_keys. A key is shown once; Odysi keeps only a fingerprint.
  • A key acts as the admin who created it, with that person’s permissions, and never with more scopes than theirs; through the API you can narrow it further. It stops working when that member is removed or suspended.
  • Creating or revoking a key is written to the audit log.

OAuth 2.1, for apps and agents that act for a person

  • Discovery. Authorization server metadata (RFC 8414) at https://crm.odysi.studio/.well-known/oauth-authorization-server, and protected resource metadata (RFC 9728) at /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-protected-resource/v2. Endpoints: /oauth/authorize, /oauth/token, /oauth/revoke, /oauth/introspect, /oauth/register.
  • Authorization code with PKCE. S256 is mandatory for public clients and plain is never accepted. The authorization response carries iss.
  • Three ways to identify your client. A Client ID Metadata Document URL (https only, cached for an hour), dynamic client registration (RFC 7591, POST /oauth/register), or an app a workspace admin registers with POST /v2/oauth/clients, which can be confidential (client_secret_post or client_secret_basic).
  • Redirect URIs must be https, except loopback (localhost, 127.0.0.1, [::1]), which matches on any port (RFC 8252), so desktop and command-line tools work.
  • Token lifetimes. Access tokens last one hour. Refresh tokens rotate on every use and last 365 days, with reuse detection and a 60-second grace for two refreshes in parallel. offline_access is accepted and ignored: you always get a refresh token.
  • One token, one resource (RFC 8707). A token requested with resource set to https://crm.odysi.studio/mcp or https://crm.odysi.studio/v2 is bound to it; sent to the other, it is refused with a message naming the right one. 401 and 403 answers carry WWW-Authenticate challenges with resource_metadata.
  • People stay in control. Every member sees and disconnects their own connections in Settings › Connected apps; admins see everyone’s.
The three scopes on a consent screen
ScopeWhat it covers
crm:readEvery fine :read scope.
crm:writecrm:read plus read-write on records, list entries, notes, tasks, comments, meetings, the email log, reports, files, and object and list configuration. Claude connects with crm:read crm:write.
crm:admincrm:write plus user_management:read-write, webhook:read-write and workspace_settings:read-write: members, API keys, webhooks and workspace settings, as far as the person’s role allows.

Fine scopes are resource:permission (for example note:read or meeting:read-write), and :read-write includes :read. A token’s scope is a ceiling, not a grant: the member’s own access still applies.

No. 04

REST /v2: envelope, errors, limits and retries

  • One envelope. Responses are { "data": … }; collections may add pagination and queries add meta. Write bodies are wrapped the same way, { "data": { … } }; query bodies are not.
  • One error shape everywhere: { "status_code", "type", "code", "message" }, with types invalid_request_error, authentication_error, permission_error, conflict_error, rate_limit_error and api_error. Messages name the field that is wrong.
  • Limits. 100 reads and 25 writes per second per token. Queries and aggregates count as reads and pay from a query-cost budget of 5,000 points per workspace every rolling 10 seconds; a bulk call pays one write per id. Over a limit you get a 429 with Retry-After in seconds.
  • Safe retries. Send an Idempotency-Key on any request that is not a GET: the response (any status below 500) is kept for 24 hours for that workspace, token and key, and a retry gets it back with Idempotency-Replayed: true. Upserts are idempotent by nature.
  • Small things that help. Request bodies up to 16 MB. Every response has an x-request-id, and an X-Request-Id you send is kept if it looks like an id (8 to 128 letters, digits, . _ : -). Bulk updates and deletes take up to 500 ids, and archiving takes up to 500 records per call.
Create a company
curl -s https://crm.odysi.studio/v2/objects/companies/records \
  -H "Authorization: Bearer $ODYSI_KEY" -H 'content-type: application/json' \
  -d '{"data":{"values":{"name":"Acme","domains":["acme.com"],"categories":["Software"]}}}'
Response (shortened)
{ "data": {
  "id": { "workspace_id": "…", "object_id": "…", "record_id": "…" },
  "values": {
    "name": [ { "value": "Acme", "active_from": "…", "active_until": null,
                "attribute_type": "text", "created_by_actor": { … } } ],
    "categories": [ { "option": { "title": "Software" }, … } ],
    "created_by": [ { "referenced_actor_type": "api-token", … } ]
  } } }
Upsert a person by email, safe to retry
curl -s -X PUT "https://crm.odysi.studio/v2/objects/people/records?matching_attribute=email_addresses" \
  -H "Authorization: Bearer $ODYSI_KEY" -H 'content-type: application/json' \
  -H 'Idempotency-Key: import-2026-10-17-row-42' \
  -d '{"data":{"values":{"email_addresses":["john.smith@acme.com"],"job_title":"CTO"}}}'
Two errors, as they come back
# 400: a filter on an attribute that does not exist
{ "status_code": 400, "type": "invalid_request_error", "code": "filter_error",
  "message": "Unknown attribute 'stagee' on 'deals'." }

# 429, with the header Retry-After: 2
{ "status_code": 429, "type": "rate_limit_error", "code": "rate_limit_exceeded",
  "message": "Retry after 2s." }

An upsert needs matching_attribute (a unique attribute); without it the answer is a 400 missing_matching_attribute. Other families follow the same pattern: list entries, merges and duplicates, meetings and call recordings, files (signed one-time upload links and 15-minute download links) and an activity feed (GET /v2/activity, newest first, only what the caller may see, with a cursor).

No. 05

The query language

POST /v2/objects/{object}/records/query takes filter, sorts, limit (up to 500) and offset. List entries use the same envelope at /v2/lists/{list}/entries/query, and list attributes win when a slug exists on both.

  • Operators: $eq, $gt, $gte, $lt, $lte, $contains, $starts_with, $ends_with, $in, $not_empty, combined with $and, $or and $not. There is no $neq: use $not, which means “no matching active value”, so “not tagged EMEA” works on multiselect attributes too.
  • Shorthand or verbose. { "name": "Acme" } and the sub-field form are both accepted and read the same way.
  • Dynamic values: { "$current_user": true } and relative dates such as { "$relative": "-90d" }.
  • Time: filter on active_from (“changed in the last 7 days”), or add $as_of to compare the value as it stood at an instant.
  • Paths: follow record references with path: [[object_or_list, attribute], …] and constraints, up to 4 hops; parent_record hops from a list entry to its record.
  • Sorting: nulls last, statuses in stage order, and record_id as the final tiebreaker, so pages are stable.
  • Cost, in every answer: meta.cost_score, its parts in meta.cost, the meta.plan and meta.elapsed_ms; add include_count: true for meta.total. The score is a base plus 2 per filter condition, 5 per sort, 15 per path hop, 10 per unindexed predicate, 25 for history and 1 per 100 rows; one query may cost up to 500.
Software companies with no interaction in 90 days
curl -s https://crm.odysi.studio/v2/objects/companies/records/query \
  -H "Authorization: Bearer $ODYSI_KEY" -H 'content-type: application/json' \
  -d '{
    "filter": { "$and": [
      { "categories": "Software" },
      { "last_interaction": { "$lt": { "$relative": "-90d" } } }
    ] },
    "sorts": [ { "attribute": "name", "direction": "asc" } ],
    "limit": 50
  }'

# Response: the rows, then what the query cost
{ "data": [ … ],
  "meta": { "cost_score": …, "cost": { "filter_conditions": 2, "sort_specs": 1, … },
            "plan": "…", "limit": 50, "offset": 0, "elapsed_ms": … } }
Two filters that only intervals make possible
# deals whose stage changed since 10 October
{ "stage": { "active_from": { "$gte": "2026-10-10T00:00:00Z" } } }

# deals that were "In Progress" at the end of June
{ "stage": { "$as_of": "2026-06-30T23:59:59Z", "$eq": "In Progress" } }
No. 06

The MCP server

  • Where and how. https://crm.odysi.studio/mcp, Streamable HTTP, stateless, POST only. Server “Odysi CRM”, with tools.listChanged.
  • Whose permissions. You connect with OAuth (Client ID Metadata Document, dynamic registration or a registered app) or with an API key, bound to one workspace. Every call runs with the permissions of the member who connected. A tool outside an OAuth token’s scope answers 403 insufficient_scope with a step-up challenge; with an API key it is a tool error.
  • Written for agents. Every tool has a title and read-only, destructive and idempotent hints. Errors come back as error <code>: <message> with isError: true, worded so the agent can fix the call. Files, Playbook imports and call audio never travel in tool arguments: they use short-lived signed links over plain HTTP (files up to 50 MB).
  • Tested clients. Claude, a ChatGPT connector, and Claude Code through loopback redirects.

New operations without reconnecting

Claude keeps a conversation’s tool list for the whole conversation, so Odysi is built never to need a reconnect. Three tools never change their name, input or annotations: list_operations returns every operation the connection can run, including ones added after the conversation started, and with name it returns one operation’s full input schema; run_read_operation and run_write_operation run any operation by name. A new ability ships as a new operation, needs no new scope, and existing tools only gain optional fields. Today tools/list returns 137 tools and there are about 140 operations.

Discover, then run by name (JSON-RPC over POST /mcp)
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": { "name": "list_operations", "arguments": { "group": "Tasks" } } }

{ "jsonrpc": "2.0", "id": 2, "method": "tools/call",
  "params": { "name": "run_read_operation",
              "arguments": { "operation": "list_tasks",
                             "arguments": { "open_only": true, "limit": 20 } } } }
Tool families
FamilyFor example
Identity and schemawhoami, describe_schema (objects, attributes, options, statuses and lists in one call), set_stage_requirements
Recordssearch_records, query_records, count_records, aggregate_records, get_records, upsert_record, merge_records, list_duplicates
Lists and viewslist_entries, add_to_list, update_entry, list_views, create_view
Reportsrun_report, create_report_doc (a page of blocks Odysi computes live), create_report_share_link
Notes and taskssearch_notes, create_note, create_note_from_template, list_tasks, update_tasks
Meetings and email loglist_meetings, create_meeting, log_email (up to 100 per call, idempotent per message_id)
Filescreate_file_upload, list_files, link_file, restore_file
Call transcriptslist_transcripts, get_transcript, attach_transcript, create_meeting_notes (Odysi Scribe writes the note)
Schema admincreate_attribute, add_statuses, create_object, create_list
Accessget_access, set_access, create_team, set_member_role
Playbookget_playbook, get_playbook_doc, search_playbook, update_playbook_doc, list_playbook_history, restore_playbook_version
Stable operationslist_operations, run_read_operation, run_write_operation

Why every conversation reads the Playbook first

The Playbook is the workspace’s operating instructions for agents: the team’s docs, with each person’s own docs on top, with history and restore. The server’s instructions tell every conversation, scheduled runs included, to call get_playbook before anything else, and to save a preference the person states with update_playbook_doc and scope=me. That way every assistant connected to the workspace follows the same rules and the same person’s preferences, instead of each keeping its own local instruction files. If the Playbook changes during a conversation, every tool result carries a note to read it again.

No. 07

Webhooks: signed, filtered and retried

Manage webhooks in Settings or with GET|POST /v2/webhooks, GET|PATCH|DELETE /v2/webhooks/{id}, the delivery log at /v2/webhooks/{id}/deliveries (last 7 days), POST …/deliveries/{delivery_id}/redeliver and POST /v2/webhooks/{id}/test (scope webhook:read-write). A webhook made with an OAuth app belongs to that app, and one made with a workspace key belongs to the workspace; neither sees the other’s.

Subscribe to stage changes only
curl -s https://crm.odysi.studio/v2/webhooks \
  -H "Authorization: Bearer $ODYSI_KEY" -H 'content-type: application/json' \
  -d '{"data":{"target_url":"https://example.com/odysi",
       "subscriptions":[{"event_type":"record.updated",
         "filter":{"$and":[{"field":"stage","operator":"changed"}]}}]}}'

# The answer includes "secret" (64 hex characters). It is shown once: store it.
  • Filters run before delivery: { "$and": [ { "field", "operator", "value" } ] } or null, with equals, not_equals, in, not_in and changed. field is a dot path into the payload, such as id.object_id; changed matches when that attribute is in updated_attributes. Each target, event type and filter combination is unique (409 uniqueness_conflict otherwise).
  • Payload. The event plus event_id, occurred_at, actor and sequence, which orders the workspace’s events. record.updated carries updated_attributes and changes ({ from, to }, or { added, removed } for multiselect); record.created carries attributes.
  • Delivery. An HTTPS POST to a target that is https and does not resolve to a private address; redirects are not followed. 5-second timeout, any 2xx counts as delivered, about 25 requests per second per target URL. Answer fast and do the work afterwards.
  • Headers: odysi-signature, odysi-signature-timestamp, odysi-event-type, odysi-webhook-id, idempotency-key (the event id, the same on every retry) and user-agent: Odysi-Webhooks/1.0.
  • Retries. 10 attempts in all: the first, then retries 30 s, 2 min, 10 min, 30 min, 1 h, 2 h, 4 h, 8 h and 16 h after each failure, about 32 hours end to end. After the last one the delivery is failed and the webhook degraded; the next success sets it back to active.
  • Redelivery. Any delivery can be queued again from the log, which keeps its status, attempts, last status code, the first 1,000 characters of your response and the last error. Delivery is at least once, so deduplicate on idempotency-key. A subscription only sees events from after it was created.
The 34 event types
FamilyEvents
Recordsrecord.created, record.updated, record.merged, record.deleted, record.restored, records.archived, records.unarchived
Listslist.created, list.updated, list.deleted, list-attribute.created, list-attribute.updated
List entrieslist-entry.created, list-entry.updated, list-entry.deleted
Attributesobject-attribute.created, object-attribute.updated
Notes and tasksnote.created, note.updated, note-content.updated, note.deleted, task.created, task.updated, task.deleted
Commentscomment.created, comment.resolved, comment.unresolved, comment.deleted
Meetings and callsmeeting.created, meeting.updated, meeting.deleted, call_recording.created
Members and accessworkspace-member.created, permission.changed

Verify the signature

The signature is hex(HMAC-SHA256(secret, "<timestamp>.<raw body>")), with the timestamp in Unix seconds from odysi-signature-timestamp. Use the secret string exactly as Odysi returned it. Check against the raw body before parsing the JSON, compare in constant time, and reject old timestamps (five minutes is a sensible window).

Fig. 3 · How a delivery is signed and checked
The signed string

The timestamp header, a dot, then the raw body, byte for byte.

HMAC-SHA256

Keyed with your webhook secret, written out as hex.

Compare

Equal to odysi-signature, in constant time: the delivery is genuine.

Node.js, matching Odysi’s scheme
import crypto from "node:crypto";
import express from "express";

// rawBody is the request body exactly as received (a Buffer), before any JSON parsing.
export function verifyOdysiWebhook(rawBody, headers, secret) {
  const signature = headers["odysi-signature"];
  const timestamp = headers["odysi-signature-timestamp"];
  if (typeof signature !== "string" || typeof timestamp !== "string") return false;
  // Refuse deliveries older (or newer) than five minutes.
  const ts = Number(timestamp);
  if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false;
  const expected = crypto.createHmac("sha256", secret)
    .update(timestamp + ".")
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(signature);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();
app.post("/odysi", express.raw({ type: "application/json" }), (req, res) => {
  if (!verifyOdysiWebhook(req.body, req.headers, process.env.ODYSI_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }
  const event = JSON.parse(req.body.toString("utf8"));
  // Deduplicate on req.headers["idempotency-key"], queue the work, answer at once.
  res.sendStatus(204);
});
No. 08

Odysi Scribe through the API

Odysi Scribe transcribes calls with named speakers and files them with the right meeting or deal. The Odysi Recorder apps for Mac and Windows use the same public /v2 API as you, signing in as public OAuth clients with PKCE and a loopback redirect. You can bring recordings in two ways.

Send audio, and Odysi Scribe transcribes it

  • POST /v2/scribe/transcripts with { "data": { "file_name", "size" } } (optionally mime, recorded_at, title, language, context, meeting_id) returns the transcript and where to upload. Send the bytes in chunks with PUT /v2/scribe/transcripts/{id}/content?offset=N, and final=1 on the last one; a small recording can go in one multipart request with the file part last.
  • Limits: chunks of 16 MiB and up to 2 GB per recording by default. A member can only have a few uploads in progress at once (429 too_many_uploads), and if transcription is not set up for you the call answers 503 scribe_not_configured. Scope record_permission:read-write.
  • Follow progress with GET /v2/scribe/transcripts/{id}: uploading, processing, then ready or failed. Uploads made with an API key or a connected app are labelled via: "agent", and only the uploader sees a transcript until it is attached to a meeting or record. Transcript events are not webhook types yet, so poll.
Start an upload
curl -s https://crm.odysi.studio/v2/scribe/transcripts \
  -H "Authorization: Bearer $ODYSI_KEY" -H 'content-type: application/json' \
  -d '{"data":{"file_name":"call-2026-10-17.m4a","size":48211234}}'

# Answer (shortened): the transcript, and where to PUT the chunks
{ "data": { "transcript": { … },
            "upload": { "url": "…", "method": "PUT", "chunk_bytes": 16777216,
                        "max_bytes": 2000000000, "expires_at": "…" } } }

Bring your own transcript

POST /v2/call_recordings takes meeting_id, media_url, duration_seconds, participants, source and transcript: { segments, raw_transcript? }, where each segment has speech, start_time and end_time in seconds and an optional speaker. You can also add the transcript later with POST /v2/call_recordings/{id}/transcript. Scope meeting:read-write; a recording emits call_recording.created once its transcript is stored.

A transcript from your own recorder
curl -s https://crm.odysi.studio/v2/call_recordings \
  -H "Authorization: Bearer $ODYSI_KEY" -H 'content-type: application/json' \
  -d '{"data":{"meeting_id":"…",
       "participants":[{"email_address":"ann@acme.example","name":"Ann"}],
       "transcript":{"segments":[
         {"speech":"Budget is approved.","start_time":0.5,"end_time":3,"speaker":{"name":"Ann"}}]}}}'

{ "data": { "call_recording_id": "…" } }
No. 09

Odysi Projects: its own host and connector

Odysi Projects, for client work once a deal is won, runs at projects.odysi.studio on the same account and workspace, with its own sign-in, its own OAuth server and discovery documents, and its own connector at https://projects.odysi.studio/mcp. Its scopes are projects:read and projects:read-write, and CRM API keys and CRM tokens are not accepted there.

  • The same pattern. tools/list returns get_project_playbook, list_operations, run_read_operation, run_write_operation and whoami, with about 100 operations behind them, shaped by role (team, client, admin). Every operation also has a REST route on the Projects host.
  • Guarded writes. A write through a connection names its source; a destructive one needs approved_by, a real person on the project; and anything a client would see waits in Drafts by default for a person to publish it. The client side can be in English or Spanish, per project.
  • On request. Odysi switches Odysi Projects on per studio. Today it runs for our own studio and our clients; if you want it for yours, talk to us.
No. 10

Audit log, export and the permissions inspector

  • Audit log. GET /v2/audit_log?limit= (admins; 200 by default, up to 5,000, newest first), or ?format=csv for a CSV file. It records configuration changes: API keys, connected apps, webhooks, members, roles and permissions, merges, exports and erasures.
  • Export everything. POST /v2/export (admins) starts a job and returns a download link: one JSON file with every record and its full history, lists, notes, tasks, comments and meetings.
  • Access per object and list. Four levels (no access, read only, read and write, full access), granted to the whole workspace, a team or a member. The most generous rule wins and there are no deny rules; admin is a settings role: it does not widen what queries and searches return (the full export, which only admins can run, includes everything). A path into an object the caller cannot read matches nothing, and references to it are hidden on read.
  • Why does Ann see this? GET /v2/objects/{object}/permissions/inspect?workspace_member_id=… (and the same under /v2/lists/{list}) answers with her level and the rules that give it. Grants are read and set with GET|PUT /v2/objects/{object}/permissions, or get_access and set_access over MCP.
No. 11

What is not there yet

  • No public OpenAPI file or docs site yet. During the beta we share the full API reference with developers on request. Until then, the API describes itself: GET /v2/self, describe_schema, list_operations and the discovery documents.
  • No client libraries. Use plain HTTP or any MCP client.
  • No parity between REST and MCP yet. Most of what you need is in both, but not every operation is; check list_operations for MCP.
  • No mail or calendar sync, by design. Your AI reads Gmail or Calendar through its own connectors and writes meetings and emails into Odysi (create_meeting, log_email).
  • No workflow engine or built-in agent. Automation is your code reacting to webhooks, or your AI.
  • No webhooks for Odysi Scribe transcripts, files or the Playbook.
  • No self-hosting. Odysi runs as a hosted service.
No. 12

How to start

  1. Get a workspace

    Sign up at crm.odysi.studio/signup. Free during the beta, for business use.

  2. Create an API key

    As an admin, after confirming your email: Settings › Connected apps › Advanced › API keys. Copy it; it is shown once.

  3. Ask who you are

    GET /v2/self, then read the schema with describe_schema or a first query.

  4. Connect your AI

    Add https://crm.odysi.studio/mcp as a custom connector, as the setup guide shows.

What Odysi Suite does for a team is in the Odysi Suite overview; connecting Claude or ChatGPT, Odysi Recorder and the Playbook step by step is in the setup guide. For the full API reference, or to switch on Odysi Projects, talk to us.

Frequently asked questions

Questions developers ask us

Is there an OpenAPI file or a full API reference?
Not in public yet. During the beta we share the full reference with developers on request. In the meantime the API describes itself: GET /v2/self says who a token is, describe_schema returns every object, attribute and list, list_operations returns every MCP operation with its input schema, and the OAuth discovery documents are public.
Does Odysi send my data to an AI model?
Odysi CRM and Odysi Projects make no generative AI calls of their own: the writing and deciding is done by your AI, through the connector, or by your code. The CRM uses AI models only to suggest how CSV columns map to fields and to flag possible duplicates. Odysi Scribe uses AI models to transcribe calls, name the speakers, file calls with their meeting or deal, polish dictation and, when you ask, write meeting notes.
How do I keep my own system in sync with Odysi?
Subscribe to webhooks for the changes you care about (34 event types, signed, retried for about 32 hours), write with upserts on a unique attribute and an Idempotency-Key, and deduplicate deliveries on their idempotency-key header. Odysi does not sync mailboxes or calendars by design: your AI reads them through its own connectors and writes what matters into Odysi.

Checked against the Odysi CRM code and its connector’s published discovery documents on 17 October 2026. Odysi Suite is in beta: limits and counts can change, and the full API reference is shared on request.

Odysi Suite
Beta · free during the beta

Get a workspace and an API key

Sign up for Odysi CRM, create a key and make your first call in a few minutes. For the full API reference, or to switch on Odysi Projects, talk to us.