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.
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.
A script, a service, Claude, ChatGPT or any MCP client.
An API key or an OAuth token, bound to one workspace, with the permissions of the person behind it.
Records, lists, notes, meetings and calls. Webhooks (dashed) tell your code what changed.
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 theapi_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) andcreated_by_actor. An update closes the old interval and opens a new one; nothing is overwritten. Read the history with?show_historic=trueon/v2/objects/{object}/records/{id}/attributes/{attribute}/values, and ask what a value was at any instant with$as_ofin a query. - Every change has an author. An actor is a
workspace-member, anapi-token(with the key’s name and the member it acts for) orsystem, 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,
PUTon a record replaces the active set andPATCHadds 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
GETon an old id returns the merged record withredirected_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:
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.
Each old value keeps its active_from and active_until: the history.
$as_ofA query reads the value as it stood at one instant (the teal line).
Its active_until is null until the next change closes it.
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/mcpand/.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
plainis never accepted. The authorization response carriesiss. - 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 withPOST /v2/oauth/clients, which can be confidential (client_secret_postorclient_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_accessis accepted and ignored: you always get a refresh token. - One token, one resource (RFC 8707). A token requested with
resourceset tohttps://crm.odysi.studio/mcporhttps://crm.odysi.studio/v2is bound to it; sent to the other, it is refused with a message naming the right one. 401 and 403 answers carryWWW-Authenticatechallenges withresource_metadata. - People stay in control. Every member sees and disconnects their own connections in Settings › Connected apps; admins see everyone’s.
| Scope | What it covers |
|---|---|
crm:read | Every fine :read scope. |
crm:write | crm: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:admin | crm: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.
REST /v2: envelope, errors, limits and retries
- One envelope. Responses are
{ "data": … }; collections may addpaginationand queries addmeta. Write bodies are wrapped the same way,{ "data": { … } }; query bodies are not. - One error shape everywhere:
{ "status_code", "type", "code", "message" }, with typesinvalid_request_error,authentication_error,permission_error,conflict_error,rate_limit_errorandapi_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-Afterin seconds. - Safe retries. Send an
Idempotency-Keyon any request that is not aGET: the response (any status below 500) is kept for 24 hours for that workspace, token and key, and a retry gets it back withIdempotency-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 anX-Request-Idyou 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.
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"]}}}'
{ "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", … } ]
} } }
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"}}}'
# 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).
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,$orand$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_ofto compare the value as it stood at an instant. - Paths: follow record references with
path: [[object_or_list, attribute], …]andconstraints, up to 4 hops;parent_recordhops from a list entry to its record. - Sorting: nulls last, statuses in stage order, and
record_idas the final tiebreaker, so pages are stable. - Cost, in every answer:
meta.cost_score, its parts inmeta.cost, themeta.planandmeta.elapsed_ms; addinclude_count: trueformeta.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.
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": … } }
# 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" } }
The MCP server
- Where and how.
https://crm.odysi.studio/mcp, Streamable HTTP, stateless,POSTonly. Server “Odysi CRM”, withtools.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_scopewith 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>withisError: 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.
{ "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 } } } }
| Family | For example |
|---|---|
| Identity and schema | whoami, describe_schema (objects, attributes, options, statuses and lists in one call), set_stage_requirements |
| Records | search_records, query_records, count_records, aggregate_records, get_records, upsert_record, merge_records, list_duplicates |
| Lists and views | list_entries, add_to_list, update_entry, list_views, create_view |
| Reports | run_report, create_report_doc (a page of blocks Odysi computes live), create_report_share_link |
| Notes and tasks | search_notes, create_note, create_note_from_template, list_tasks, update_tasks |
| Meetings and email log | list_meetings, create_meeting, log_email (up to 100 per call, idempotent per message_id) |
| Files | create_file_upload, list_files, link_file, restore_file |
| Call transcripts | list_transcripts, get_transcript, attach_transcript, create_meeting_notes (Odysi Scribe writes the note) |
| Schema admin | create_attribute, add_statuses, create_object, create_list |
| Access | get_access, set_access, create_team, set_member_role |
| Playbook | get_playbook, get_playbook_doc, search_playbook, update_playbook_doc, list_playbook_history, restore_playbook_version |
| Stable operations | list_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.
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.
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, withequals,not_equals,in,not_inandchanged.fieldis a dot path into the payload, such asid.object_id;changedmatches when that attribute is inupdated_attributes. Each target, event type and filter combination is unique (409uniqueness_conflictotherwise). - Payload. The event plus
event_id,occurred_at,actorandsequence, which orders the workspace’s events.record.updatedcarriesupdated_attributesandchanges({ from, to }, or{ added, removed }for multiselect);record.createdcarriesattributes. - Delivery. An HTTPS
POSTto 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) anduser-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
failedand the webhookdegraded; the next success sets it back toactive. - 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.
| Family | Events |
|---|---|
| Records | record.created, record.updated, record.merged, record.deleted, record.restored, records.archived, records.unarchived |
| Lists | list.created, list.updated, list.deleted, list-attribute.created, list-attribute.updated |
| List entries | list-entry.created, list-entry.updated, list-entry.deleted |
| Attributes | object-attribute.created, object-attribute.updated |
| Notes and tasks | note.created, note.updated, note-content.updated, note.deleted, task.created, task.updated, task.deleted |
| Comments | comment.created, comment.resolved, comment.unresolved, comment.deleted |
| Meetings and calls | meeting.created, meeting.updated, meeting.deleted, call_recording.created |
| Members and access | workspace-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).
The timestamp header, a dot, then the raw body, byte for byte.
Keyed with your webhook secret, written out as hex.
Equal to odysi-signature, in constant time: the delivery is genuine.
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);
});
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/transcriptswith{ "data": { "file_name", "size" } }(optionallymime,recorded_at,title,language,context,meeting_id) returns the transcript and where to upload. Send the bytes in chunks withPUT /v2/scribe/transcripts/{id}/content?offset=N, andfinal=1on the last one; a small recording can go in one multipart request with thefilepart 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 503scribe_not_configured. Scoperecord_permission:read-write. - Follow progress with
GET /v2/scribe/transcripts/{id}:uploading,processing, thenreadyorfailed. Uploads made with an API key or a connected app are labelledvia: "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.
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.
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": "…" } }
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/listreturnsget_project_playbook,list_operations,run_read_operation,run_write_operationandwhoami, 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 needsapproved_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.
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=csvfor 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 withGET|PUT /v2/objects/{object}/permissions, orget_accessandset_accessover MCP.
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_operationsand 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_operationsfor 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.
How to start
- Get a workspace
Sign up at crm.odysi.studio/signup. Free during the beta, for business use.
- Create an API key
As an admin, after confirming your email: Settings › Connected apps › Advanced › API keys. Copy it; it is shown once.
- Ask who you are
GET /v2/self, then read the schema withdescribe_schemaor a first query. - Connect your AI
Add
https://crm.odysi.studio/mcpas 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.
Questions developers ask us
Is there an OpenAPI file or a full API reference?
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?
How do I keep my own system in sync with Odysi?
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.