Skip to content

Commit c703fa5

Browse files
committed
fix(coda): refine setup docs and verification coverage
1 parent ce7a6f5 commit c703fa5

6 files changed

Lines changed: 72 additions & 47 deletions

File tree

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,64 @@
11
---
22
title: Coda
3-
description: Index Coda pages and tables with each teammate's source permissions
3+
description: Connect Coda documents while preserving each teammate's access
44
---
55

6-
import { Callout } from 'fumadocs-ui/components/callout'
6+
import { Step, Steps } from 'fumadocs-ui/components/steps'
7+
import { Image } from '@/components/ui/image'
78

8-
An organization admin connects a Coda API token. Sim indexes the selected documents and matches their sharing permissions to teammates' verified Sim email addresses. Teammates do not need a personal OAuth connection for this source.
9+
An organization admin connects Coda once. Teammates search using their verified Sim email address; they do not need a personal Coda connection. Their email must match the address Coda reports.
910

10-
## Set up the source
11+
## Connect Coda
1112

12-
1. In Coda, open **Account settings → API connections** and generate an API token for the account that will index your documents. Coda's current app may be labeled **Superhuman Docs**. The account must be able to read the documents and their sharing permissions.
13-
2. In Sim, open **Settings → Sources → Add source → Coda**. Add or select a Coda service account credential and paste the API token when prompted.
14-
3. Select **Documents**, or use the switch beside that field to enter **Document IDs**, separated by commas. In a Coda URL such as `https://coda.io/d/_dAbCDeFGH`, the document ID is `AbCDeFGH`. Select up to 100 documents per source. The picker shows documents the token owner can access and has opened; use IDs for other Enterprise organization documents.
15-
4. Leave **Enterprise organization ID** empty for ordinary token-based access. For organization-wide access, use an Enterprise organization administrator's token and enter the organization ID returned by Coda's [Admin API](https://coda.io/developers/apis/admin/v1).
16-
5. Select **Connect & Sync**. Open the source's **Documents** and **Sync history** to check its progress.
13+
<Steps>
14+
<Step>
1715

18-
API tokens do not refresh automatically. When rotating a token, add the replacement credential, update the source, and verify a sync before revoking the old token. Indexing only reads Coda content; a broadly authorized token may allow other operations in Coda.
16+
### Create an API token
1917

20-
## Document selection and content
18+
Sign in to Coda with a dedicated integration account that can read the documents and their sharing permissions. Open **Account settings → API connections → Generate API token**, name the token, and copy it. Coda's current app may be labeled **Superhuman Docs**.
2119

22-
Explicit document IDs provide the clearest sync scope and include accessible documents the token owner has not opened. If IDs are empty, ordinary discovery includes only accessible documents the token owner has previously opened. Enterprise discovery uses the organization's document list.
20+
Use a standard REST API token. Do not select the MCP restriction.
2321

24-
Discovery pages can change order while syncing. Sim therefore does not delete existing indexed content solely because it disappears from a discovery listing. Use explicit document IDs when reliable deletion reconciliation is required. Discovery runs may report incomplete reconciliation; this does not mean their returned content failed to index.
22+
<Image className="mx-auto h-auto w-full max-w-2xl" src="/static/search/coda-api-token.jpg" alt="Coda API connections with the Generate new token dialog" width={1280} height={720} />
2523

26-
| Connection | Indexed content |
27-
| --- | --- |
28-
| Ordinary API token | Visible canvas page text and base tables, including all listed columns and rows. Hidden pages, embedded pages, synced pages, and duplicate table views are excluded. |
29-
| Enterprise organization | Page content returned by the Admin API's `LossyPlainText` format. Tables are represented only to the extent included in that export; they are not separately read through the public table API. |
24+
</Step>
25+
<Step>
3026

31-
Comments, attachments, images, and arbitrary embedded applications are not extracted. The connector stops with an error rather than indexing truncated content when a provider response, document, or pagination limit is exceeded. If the Enterprise API omits a document revision timestamp, Sim refreshes that document on each crawl.
27+
### Add the source
3228

33-
Document edits invalidate the pages and tables within that document. A full resync also refreshes rendered content whose dependencies may have changed without changing the document revision. This costs more API calls and processing than a regular sync. Ordinary unchanged documents retain their indexed content while their permissions are refreshed.
29+
In Sim, open **Settings → Sources → Add source → Coda**. Add or select a Coda service account credential and enter the token when prompted.
3430

35-
## How teammate access works
31+
| Field | What to enter |
32+
|---|---|
33+
| Documents | Select documents, or switch to **Document IDs** and enter up to 100 IDs separated by commas. For `https://coda.io/d/_dAbCDeFGH`, the ID is `AbCDeFGH`. |
34+
| Enterprise organization ID | Leave blank for ordinary access. For organization-wide indexing, enter the ID from Coda's [List organizations API](https://coda.io/developers/apis/admin/v1#operation/listOrganizations) and use an Enterprise organization administrator's token. Workspace admin access alone is insufficient. |
3635

37-
| Coda permission | Ordinary token | Enterprise organization |
38-
| --- | --- | --- |
39-
| Owner or direct email share | Matches the verified Sim email | Matches active users in the organization directory |
40-
| Group share | Excluded | Resolves group members through the Admin API |
41-
| Workspace share | Excluded | Resolves that Coda workspace's members |
42-
| Domain share | Excluded | Matches active organization-directory users in that exact domain |
43-
| Anyone with the link | Does not grant Search access | Does not grant Search access |
36+
The picker shows accessible documents the token owner has opened. Use IDs for other accessible documents. With no selection, Sim discovers the token owner's opened documents, or the organization's documents in Enterprise mode. Use explicit IDs when deleted documents must reliably be removed from the index; discovery listings alone are not authoritative for deletion.
37+
38+
</Step>
39+
<Step>
40+
41+
### Sync and verify
4442

45-
Enterprise mode excludes deactivated or deleted organization users. Directly shared external guests absent from the organization directory are also excluded. A guest explicitly returned in a group's or workspace's membership can receive that group's or workspace's access, unless the organization directory marks them inactive.
43+
Select **Connect & Sync**. Open the source's **Documents** and **Sync history** to check indexing. Content and permission changes appear after synchronization.
4644

47-
Ordinary token mode cannot independently check organization-level Coda account deactivation. Manage the person's Sim membership as well, or use Enterprise directory synchronization when that check is required.
45+
To rotate the token, add the replacement credential, update the source, and verify a sync before revoking the old token.
4846

49-
Unknown permission types never become broad access. Sim admin status does not override Coda permissions, and a workspace API key does not inherit an individual's Coda access. A source created in **workspace access** mode inside an ordinary knowledge base intentionally shares its indexed content with that workspace; organization Search requires source permissions.
47+
</Step>
48+
</Steps>
5049

51-
<Callout type="info">
52-
Permission changes take effect after synchronization. Each sync reads sharing again, including for unchanged content. If sharing or directory data cannot be completely verified, the connector fails closed through Sim's permission-sync handling. A verified Sim email must match the address Coda reports; aliases are not inferred.
53-
</Callout>
50+
## Content and access
51+
52+
Ordinary connections index visible canvas text and base-table rows. Enterprise connections index the Admin API's plaintext page exports; tables are included only as represented in those exports. Attachments, comments, images, and remote embeds are not extracted.
53+
54+
| Permission | Ordinary connection | Enterprise connection |
55+
|---|---|---|
56+
| Owner or direct email share | Matching verified Sim email | Matching active organization user |
57+
| Group, workspace, or domain share | Not expanded | Resolved through the Admin API |
58+
| Anyone with the link | Does not grant Search access | Does not grant Search access |
5459

55-
## Troubleshooting
60+
Enterprise mode excludes deactivated and deleted users. Direct-share guests absent from the organization directory are excluded; guests explicitly listed in a group or workspace may receive that membership's access. Ordinary connections cannot check Coda organization deactivation, so manage departing users' Sim membership too.
5661

57-
| Problem | What to check |
58-
| --- | --- |
59-
| No documents found | Open the documents as the token owner or enter their IDs explicitly. |
60-
| Enterprise setup fails | Confirm the account is an Enterprise organization administrator and the organization ID is correct. An ordinary workspace admin token is insufficient. |
61-
| Content exists but a teammate cannot search it | Check the verified Sim email, Coda share, selected scope, and sync history. Ordinary mode cannot resolve group, domain, or workspace-only shares. |
62-
| Cross-domain sharing is prohibited | Coda's organization policy blocks that share. Use a permitted test account or an existing workspace that permits the intended share. |
63-
| New document temporarily unavailable | Coda may return `409` while initializing its API snapshot. The connector retries; retry the sync if initialization takes longer. |
64-
| Oversized or malformed response | The sync preserves its failure state instead of treating a partial response as complete. Narrow the scope or contact your operator. |
62+
Sim refreshes permissions even when content is unchanged. Unverified permissions never become broad access, and Sim admin status does not override Coda sharing. For setup failures or missing results, check the token's access, Enterprise admin role if applicable, the teammate's verified email, and **Sync history**.
6563

66-
The connector uses Coda's [public API](https://coda.io/developers/apis/v1) and optional [Enterprise Admin API](https://coda.io/developers/apis/admin/v1). It needs no additional OAuth app configuration on self-hosted Sim.
64+
See Coda's [public API](https://coda.io/developers/apis/v1) and [Enterprise Admin API](https://coda.io/developers/apis/admin/v1).
47.7 KB
Loading

apps/sim/connectors/coda/README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,31 @@
22

33
## Precedent and authentication
44

5-
[Glean](https://www.glean.com/connectors/coda) supports Coda document/page search with source permissions. [Onyx's connector](https://github.com/onyx-dot-app/onyx/blob/main/backend/onyx/connectors/coda/connector.py) reads page text and table rows using a Coda API token; its content connector is not evidence that ordinary tokens expose an organization directory.
5+
| Reference | Verified behavior | What it establishes for Sim |
6+
| --- | --- | --- |
7+
| [Glean Coda search](https://www.glean.com/connectors/coda) | Native document/page indexing with query-time source permissions. | The same access-control objective. The public page does not specify the crawler's credentials, endpoints, directory expansion, or deletion strategy; exact implementation parity is not established. |
8+
| [Glean Coda agent service credential](https://docs.glean.com/administration/agent-identity/coda) | A dedicated Coda user and an MCP-restricted token for agent tools. | Dedicated identity and credential rotation are useful operational precedent. This is a separate MCP integration, not the REST indexing setup; its MCP token restriction must not be copied into Sim's connector instructions. |
9+
| [Onyx Coda connector](https://github.com/onyx-dot-app/onyx/blob/5fe6573c3c155e1a75b51de32d4988ee6c82164e/backend/onyx/connectors/coda/connector.py) | A bearer token, optional workspace scope, page content, and table rows through the public REST API. | Direct precedent for ordinary content ingestion. Sim uses bounded deferred hydration rather than collecting the entire source before indexing. |
10+
11+
Onyx's [test setup](https://github.com/onyx-dot-app/onyx/blob/5fe6573c3c155e1a75b51de32d4988ee6c82164e/backend/tests/daily/connectors/coda/README.md) also uses an API token generated in account settings. Its reviewed Coda implementation has no source ACL or directory calls, and its [permission-sync registry](https://github.com/onyx-dot-app/onyx/blob/5fe6573c3c155e1a75b51de32d4988ee6c82164e/backend/ee/onyx/external_permissions/sync_params.py) has no Coda entry. It therefore does not establish Enterprise permission parity. Coda's own API documentation is the authority for Sim's Enterprise path.
612

713
This connector reuses Sim's existing `coda-service-account` token credential and its selector. The metadata's `oauth` discriminator means “use a stored credential” in the shared connector framework. The actual credential is an API token, with no OAuth authorization or refresh flow. Personal credential-group enrollment is not exposed because that flow currently requires OAuth. Admin indexing matches provider-reported grants to verified Sim emails instead.
814

15+
## API contract audit
16+
17+
All connector requests are GET requests authenticated with `Authorization: Bearer`. The following operation IDs were checked against the OpenAPI specifications embedded in Coda's [public reference](https://coda.io/developers/apis/v1) and [Admin reference](https://coda.io/developers/apis/admin/v1), including query parameters, response fields, pagination, and allowed formats. None of these operations is deprecated.
18+
19+
| API | Operations used |
20+
| --- | --- |
21+
| Public identity and discovery | `whoami`, `listDocs`, `getDoc` |
22+
| Public content | `listPages`, `getPage`, `listPageContent`, `listTables`, `getTable`, `listColumns`, `listRows` |
23+
| Public permissions | `getPermissions` |
24+
| Admin discovery | `getOrganization`, `listDocs` with `docIds` or `docAvailabilityState` and `fetchPermissionsMode=none` |
25+
| Admin content and permissions | `listPagesV2`, `getPageV2` with `outputFormat=LossyPlainText`, `listDocPermissionsV2` |
26+
| Admin directory | `listOrgUsers`, `listGroups`, `listGroupMembers`, `listWorkspaces`, `listWorkspaceUsers` |
27+
28+
The Admin API requires an [Enterprise organization administrator](https://help.superhuman.com/hc/en-us/articles/46210125237901-Intro-to-Superhuman-Docs-Admin-API). Ordinary workspace admin access does not satisfy that requirement. All network calls use the shared secure fetch/retry path and fixed API origins; the connector uses no browser session, private endpoint, credential impersonation, or provider permission mutation. Synthetic directory groups are Sim's internal representation of documented grants, not invented Coda API objects. Size and crawl limits below are Sim safeguards, not claimed Coda service limits.
29+
930
## Access tradeoffs
1031

1132
- Without an organization ID, only ownership and direct email grants establish individual access. Workspace, domain, group, and unknown principals are not expanded. Link possession never grants Search access. The public API does not provide organization account status; this mode cannot independently detect organization-level deactivation while a direct grant remains. Sim membership lifecycle must also be managed.

apps/sim/connectors/permission-scoped-listing.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ describe('permission-scoped connector listings', () => {
4141
it('offers Search only for reviewed source and permission capabilities', () => {
4242
const search = Object.values(CONNECTOR_META_REGISTRY).filter((meta) => meta.search)
4343
expect(search.map((meta) => meta.id).sort()).toEqual([
44+
'coda',
4445
'confluence',
4546
'github',
4647
'gitlab',

apps/sim/lib/integrations/credential-display.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const EXPECTED_COVERAGE: Record<string, string[]> = {
4949
'calcom-service-account': ['cal-com'],
5050
'claude-platform-service-account': [],
5151
'clickup-service-account': ['clickup'],
52-
'coda-service-account': [],
52+
'coda-service-account': ['coda'],
5353
'github-app-installation': ['github'],
5454
'google-service-account': [
5555
'gmail',

apps/sim/lib/knowledge/__integration__/coda-live.integration.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ describe
8686
let documentId: string
8787
let token: string
8888
let fixture: z.infer<typeof fixtureSchema>
89+
let fixtureValidated = false
8990
let credentialId = generateId()
9091
const principal = (userId: string): Principal => ({
9192
kind: 'session',
@@ -173,6 +174,7 @@ describe
173174
) {
174175
throw new Error('Refusing to change sharing on a non-fixture document')
175176
}
177+
fixtureValidated = true
176178
await revokeShare()
177179
await waitForAcl(false)
178180
ids = await seedKnowledgeAclFixture()
@@ -263,7 +265,10 @@ describe
263265

264266
afterAll(async () => {
265267
try {
266-
if (fixture && allowSharing) await revokeShare()
268+
if (fixtureValidated && allowSharing) {
269+
await revokeShare()
270+
await waitForAcl(false)
271+
}
267272
} finally {
268273
if (ids && !uiFixturePath) {
269274
const rows = await db
@@ -300,7 +305,7 @@ describe
300305
}
301306
await db.$client.end()
302307
}
303-
})
308+
}, 60_000)
304309

305310
it('indexes through admin setup and restricts private content to its owner', async () => {
306311
expect(metrics.embeddingCalls).toBeGreaterThan(0)

0 commit comments

Comments
 (0)