@zackbart/connecta 0.36.0 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/README.md +1 -1
- package/bin/version.mjs +1 -1
- package/dist/http/hatch-paging.js +2 -2
- package/dist/http/paging.js +3 -88
- package/dist/http/provider.js +1 -2
- package/dist/http/tools.js +1 -23
- package/dist/program-writes.js +0 -1
- package/dist/providers/_shared/sensitive-fields.js +0 -16
- package/dist/providers/breeze/api.js +2 -2
- package/dist/providers/ccb/api.js +1 -1
- package/dist/providers/cloudflare/api.js +8 -8
- package/dist/providers/cloudflare/lib/config.d.ts +2 -1
- package/dist/providers/docs/api.js +1 -1
- package/dist/providers/drive/api.js +1 -1
- package/dist/providers/drive/lib/hatch.js +1 -1
- package/dist/providers/drive/tools.js +1 -1
- package/dist/providers/forms/api.js +1 -1
- package/dist/providers/forms/lib/hatch.js +1 -1
- package/dist/providers/forms/tools.js +1 -1
- package/dist/providers/github/api.js +1 -1
- package/dist/providers/github/lib/discovery.js +2 -2
- package/dist/providers/gmail/api.js +1 -1
- package/dist/providers/granola/api.js +155 -0
- package/dist/providers/granola/index.d.ts +10 -0
- package/dist/providers/granola/index.js +68 -0
- package/dist/providers/granola/openapi.generated.js +30 -0
- package/dist/providers/granola/safety.js +37 -0
- package/dist/providers/granola/skill.generated.js +5 -0
- package/dist/providers/granola/tools.js +2 -0
- package/dist/providers/overflow/api.js +26 -26
- package/dist/providers/planning-center/api.js +4 -4
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +2 -2
- package/dist/providers/quickbooks/lib/query.js +1 -1
- package/dist/providers/quickbooks/safety.js +1 -1
- package/dist/providers/sheets/api.js +1 -1
- package/dist/providers/sheets/lib/hatch.js +2 -2
- package/dist/providers/sheets/lib/requests.js +1 -1
- package/dist/providers/slides/api.js +1 -1
- package/dist/providers/stripe/api.js +7 -7
- package/dist/providers/tithely/api.js +2 -2
- package/dist/providers/vercel/api.js +12 -12
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/routes/mcp.js +1 -1
- package/dist/runtime/admission.js +0 -12
- package/dist/storage/keys.js +2 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +124 -74
- package/documentation/auth.md +9 -2
- package/documentation/code-mode.md +6 -6
- package/documentation/meta-tools.md +5 -2
- package/documentation/operator-tests.md +11 -2
- package/documentation/operator-ui.md +1 -2
- package/documentation/providers.md +110 -0
- package/package.json +6 -2
- package/templates/node/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** One workspace-owned Granola REST API key, managed in the encrypted vault. */
|
|
2
|
+
export interface GranolaOptions {
|
|
3
|
+
purpose: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
instructions?: string;
|
|
6
|
+
maxResultBytes?: number;
|
|
7
|
+
/** Common connector restriction; no value enables unreviewed operations or writes. */
|
|
8
|
+
readOnly?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare const granola: import("../../provider.js").ProviderFactory<GranolaOptions>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { keys, optionsOf } from "../../config-schema.js";
|
|
2
|
+
import { defineApiProvider } from "../../http/provider.js";
|
|
3
|
+
import { GranolaApi, authenticate, binding, errors, index, pages, probe, result, safety, verdicts } from "./api.js";
|
|
4
|
+
import { granolaTools } from "./tools.js";
|
|
5
|
+
import { skill } from "./skill.generated.js";
|
|
6
|
+
export const granola = defineApiProvider({
|
|
7
|
+
name: "granola",
|
|
8
|
+
title: "Granola",
|
|
9
|
+
readme: "Granola",
|
|
10
|
+
options: optionsOf()(keys("purpose", "title", "instructions", "maxResultBytes", "readOnly")),
|
|
11
|
+
skill,
|
|
12
|
+
bundle: {
|
|
13
|
+
baselineGzip: 360040,
|
|
14
|
+
note: "./providers/granola standalone measured 360,040 B gzip with Homebrew Node 22 on 2026-10-27. This includes the shared Effect provider kit, paid once per deployment; provider entries are reported, not capped, and standalone sizes are not additive.",
|
|
15
|
+
},
|
|
16
|
+
vendor: GranolaApi,
|
|
17
|
+
baseUrl: () => "https://public-api.granola.ai",
|
|
18
|
+
description: (options) => `Granola workspace meeting notes: ${options.purpose}`,
|
|
19
|
+
authScope: () => "shared",
|
|
20
|
+
maxResponseBytes: 4 * 1024 * 1024,
|
|
21
|
+
timeoutMs: 30_000,
|
|
22
|
+
headers: { Accept: "application/json" },
|
|
23
|
+
idempotencyKey: false,
|
|
24
|
+
defaultCallAdmission: {
|
|
25
|
+
rules: [
|
|
26
|
+
{
|
|
27
|
+
maxConcurrency: 2,
|
|
28
|
+
maxQueueSize: 25,
|
|
29
|
+
queueTimeoutMs: 5_000,
|
|
30
|
+
retryAfterMs: 1_000,
|
|
31
|
+
budget: { kind: "rolling-window", maxCalls: 25, windowMs: 5_000 },
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
credential: {
|
|
36
|
+
label: "Granola workspace API key",
|
|
37
|
+
description: "Workspace-owned key from Settings → Connectors → Workspace API keys (Business or Enterprise admin). Public notes and API-enabled spaces only; not a personal key or MCP OAuth token. Stored encrypted and sent only to public-api.granola.ai.",
|
|
38
|
+
placeholder: "grn_…",
|
|
39
|
+
},
|
|
40
|
+
authenticate,
|
|
41
|
+
errors,
|
|
42
|
+
safety,
|
|
43
|
+
result,
|
|
44
|
+
probe,
|
|
45
|
+
guide: (options) => ({
|
|
46
|
+
heading: "Granola usage",
|
|
47
|
+
summary: "Read workspace notes and transcripts; discover accessible spaces and folders before filtering.",
|
|
48
|
+
context: [
|
|
49
|
+
`Workspace purpose: ${options.purpose}`,
|
|
50
|
+
"One shared workspace API key over REST. This connection exposes only reviewed reads.",
|
|
51
|
+
],
|
|
52
|
+
required: true,
|
|
53
|
+
}),
|
|
54
|
+
hatch: {
|
|
55
|
+
name: "granola",
|
|
56
|
+
title: "Granola",
|
|
57
|
+
index,
|
|
58
|
+
verdicts,
|
|
59
|
+
pages,
|
|
60
|
+
pageCursor: "argument",
|
|
61
|
+
binding,
|
|
62
|
+
pathExample: "/v1/notes",
|
|
63
|
+
notes: () => ({
|
|
64
|
+
read: "Omit query.cursor initially; pass page.nextCursor unchanged as top-level cursor with the same path and query. Note/folder/space page_size defaults to 10 (maximum 30); transcripts default to 50 (maximum 100).",
|
|
65
|
+
}),
|
|
66
|
+
},
|
|
67
|
+
tools: granolaTools,
|
|
68
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { compactOpenApi } from "../_shared/rest/compact-index.js";
|
|
2
|
+
export const openapi = compactOpenApi({
|
|
3
|
+
source: "https://docs.granola.ai/api-reference/openapi.json",
|
|
4
|
+
revision: "sha256:3f1a93ae6380",
|
|
5
|
+
digest: "sha256:3f1a93ae6380feb5297df8115855486224f82bea236eaf1fd916072843f5759d",
|
|
6
|
+
version: "1.0.0",
|
|
7
|
+
servers: ["https://public-api.granola.ai"],
|
|
8
|
+
tags: ["audit", "folders", "legal-holds", "notes", "spaces", "webhook-endpoints"],
|
|
9
|
+
ops: [
|
|
10
|
+
["GET", "/v1/audit", "get/v1/audit", "List audit events", 0],
|
|
11
|
+
["GET", "/v1/folders", "get/v1/folders", "List folders", 1],
|
|
12
|
+
["GET", "/v1/legal-holds", "get/v1/legal-holds", "List legal holds", 2],
|
|
13
|
+
["POST", "/v1/legal-holds", "post/v1/legal-holds", "Create legal hold", 2],
|
|
14
|
+
["GET", "/v1/legal-holds/{hold_id}", "get/v1/legal-holds/{hold_id}", "Get legal hold", 2],
|
|
15
|
+
["PATCH", "/v1/legal-holds/{hold_id}", "patch/v1/legal-holds/{hold_id}", "Update legal hold", 2],
|
|
16
|
+
["DELETE", "/v1/legal-holds/{hold_id}", "delete/v1/legal-holds/{hold_id}", "Release legal hold", 2],
|
|
17
|
+
["GET", "/v1/legal-holds/{hold_id}/custodians", "get/v1/legal-holds/{hold_id}/custodians", "List legal hold custodians", 2],
|
|
18
|
+
["POST", "/v1/legal-holds/{hold_id}/custodians", "post/v1/legal-holds/{hold_id}/custodians", "Add legal hold custodians", 2],
|
|
19
|
+
["DELETE", "/v1/legal-holds/{hold_id}/custodians/{custodian_id}", "delete/v1/legal-holds/{hold_id}/custodians/{custodian_id}", "Remove legal hold custodian", 2],
|
|
20
|
+
["GET", "/v1/notes", "get/v1/notes", "List notes", 3],
|
|
21
|
+
["GET", "/v1/notes/{note_id}", "get/v1/notes/{note_id}", "Get note", 3],
|
|
22
|
+
["GET", "/v1/notes/{note_id}/transcript", "get/v1/notes/{note_id}/transcript", "Get transcript", 3],
|
|
23
|
+
["GET", "/v1/spaces", "get/v1/spaces", "List spaces", 4],
|
|
24
|
+
["GET", "/v1/webhook-endpoints", "get/v1/webhook-endpoints", "List webhook endpoints", 5],
|
|
25
|
+
["POST", "/v1/webhook-endpoints", "post/v1/webhook-endpoints", "Create webhook endpoint", 5],
|
|
26
|
+
["PATCH", "/v1/webhook-endpoints/{webhook_endpoint_id}", "patch/v1/webhook-endpoints/{webhook_endpoint_id}", "Update webhook endpoint", 5],
|
|
27
|
+
["DELETE", "/v1/webhook-endpoints/{webhook_endpoint_id}", "delete/v1/webhook-endpoints/{webhook_endpoint_id}", "Delete webhook endpoint", 5],
|
|
28
|
+
],
|
|
29
|
+
fields: "{\"p\":[\"attachments\",\"attachments[].height\",\"attachments[].id\",\"attachments[].object\",\"attachments[].type\",\"attachments[].url\",\"attachments[].width\",\"attendees\",\"attendees[].email\",\"attendees[].name\",\"calendar_event\",\"covers_entire_workspace\",\"created_at\",\"created_by\",\"cursor\",\"custodian_count\",\"custodians\",\"custodians[].added_at\",\"custodians[].id\",\"custodians[].object\",\"custodians[].removed_at\",\"custodians[].user\",\"deleted\",\"deleted_at\",\"description\",\"enabled\",\"events\",\"events[].action\",\"events[].actor\",\"events[].collected_at\",\"events[].context\",\"events[].data\",\"events[].id\",\"events[].object\",\"events[].occurred_at\",\"folder_ids\",\"folder_membership\",\"folder_membership[].id\",\"folder_membership[].name\",\"folder_membership[].object\",\"folder_membership[].parent_folder_id\",\"folder_membership[].space_id\",\"folders\",\"folders[].id\",\"folders[].name\",\"folders[].object\",\"folders[].parent_folder_id\",\"folders[].space_id\",\"hasMore\",\"id\",\"legal_holds\",\"legal_holds[].covers_entire_workspace\",\"legal_holds[].created_at\",\"legal_holds[].custodian_count\",\"legal_holds[].description\",\"legal_holds[].id\",\"legal_holds[].name\",\"legal_holds[].object\",\"legal_holds[].released_at\",\"name\",\"notes\",\"notes[].created_at\",\"notes[].deleted_at\",\"notes[].id\",\"notes[].object\",\"notes[].owner\",\"notes[].title\",\"notes[].updated_at\",\"object\",\"owner\",\"private_notes_markdown\",\"private_notes_text\",\"released\",\"released_at\",\"removed\",\"removed_at\",\"scopes\",\"signing_secret\",\"space_membership\",\"space_membership[].id\",\"space_membership[].name\",\"space_membership[].object\",\"spaces\",\"spaces[].id\",\"spaces[].name\",\"spaces[].object\",\"summary_markdown\",\"summary_text\",\"title\",\"transcript\",\"transcript[].end_time\",\"transcript[].speaker\",\"transcript[].start_time\",\"transcript[].text\",\"updated_at\",\"url\",\"url_redacted\",\"web_url\",\"webhook_endpoints\",\"webhook_endpoints[].created_at\",\"webhook_endpoints[].created_by\",\"webhook_endpoints[].enabled\",\"webhook_endpoints[].events\",\"webhook_endpoints[].folder_ids\",\"webhook_endpoints[].id\",\"webhook_endpoints[].object\",\"webhook_endpoints[].scopes\",\"webhook_endpoints[].url\",\"webhook_endpoints[].url_redacted\"],\"d\":[[14,26,27,28,29,30,31,32,33,34,48],[14,42,43,44,45,46,47,48],[14,48,50,51,52,53,54,55,56,57,58],[11,12,15,24,49,59,68,73],[49,68,72,73],[14,16,17,18,19,20,21,48],[16,17,18,19,20,21],[49,68,74,75],[14,48,60,61,62,63,64,65,66,67],[0,1,2,3,4,5,6,7,8,9,10,12,23,36,37,38,39,40,41,49,68,69,70,71,78,79,80,81,86,87,88,89,90,91,92,93,94,97],[14,48,89,90,91,92,93],[14,48,82,83,84,85],[98,99,100,101,102,103,104,105,106,107,108],[12,13,25,26,35,49,68,76,77,95,96],[12,13,25,26,35,49,68,76,95,96],[22,49,68]],\"o\":[0,1,2,3,3,3,4,5,6,7,8,9,10,11,12,13,14,15]}",
|
|
30
|
+
}, { "length": 3267, "definitions": "[{\"a\":[{\"t\":\"string\",\"f\":\"date\"},{\"t\":\"string\",\"f\":\"date-time\"}]},{\"t\":\"object\",\"p\":{\"email\":{\"t\":\"string\",\"f\":\"email\"},\"id\":{\"t\":\"string\"}}},{\"t\":\"string\",\"e\":[\"note.access_granted\",\"note.edited\",\"note.generated\"]}]", "parameters": ["[\"action\",\"query\",0,{\"t\":\"string\"}]", "[\"occurred_before\",\"query\",0,{\"$\":0}]", "[\"occurred_after\",\"query\",0,{\"$\":0}]", "[\"cursor\",\"query\",0,{\"t\":\"string\"}]", "[\"page_size\",\"query\",0,{\"t\":\"integer\",\"l\":1,\"u\":30}]", "[\"created_before\",\"query\",0,{\"$\":0}]", "[\"created_after\",\"query\",0,{\"$\":0}]", "[\"updated_after\",\"query\",0,{\"$\":0}]", "[\"folder_id\",\"query\",0,{\"t\":\"string\"}]", "[\"space_id\",\"query\",0,{\"t\":\"string\"}]", "[\"include\",\"query\",0,{\"t\":\"string\",\"e\":[\"transcript\"]}]", "[\"page_size\",\"query\",0,{\"t\":\"integer\",\"l\":1,\"u\":100}]"], "operations": [[[0, 1, 2, 3, 4], "0"], [[3, 4], "0"], [[3, 4], "0"], [[], "[\"application/json\",{\"t\":\"object\",\"p\":{\"name\":{\"t\":\"string\",\"d\":\"A name for the matter this hold preserves\"},\"description\":{\"t\":[\"string\",\"null\"],\"d\":\"Free text describing the matter\"},\"covers_entire_workspace\":{\"t\":\"boolean\",\"d\":\"Whether the hold covers every member of your workspace, current and future.\"},\"custodians\":{\"t\":\"array\",\"d\":\"Users to name individually when the hold is created\",\"i\":{\"$\":1}}},\"r\":[\"name\",\"covers_entire_workspace\"]}]"], 0, [[], "[\"application/json\",{\"t\":\"object\",\"p\":{\"name\":{\"t\":\"string\",\"d\":\"A new name for the matter\"},\"description\":{\"t\":[\"string\",\"null\"],\"d\":\"New free text for the matter, or null to clear it\"},\"covers_entire_workspace\":{\"t\":\"boolean\",\"d\":\"Whether the hold covers every member of your workspace.\"}}}]"], 0, [[3, 4], "0"], [[], "[\"application/json\",{\"t\":\"object\",\"p\":{\"custodians\":{\"t\":\"array\",\"d\":\"Users to name on this hold\",\"i\":{\"$\":1}}},\"r\":[\"custodians\"]}]"], 0, [[5, 6, 7, 8, 3, 4, 9], "0"], [[10], "0"], [[3, 11], "0"], [[3, 4], "0"], 0, [[], "[\"application/json\",{\"t\":\"object\",\"p\":{\"url\":{\"t\":\"string\",\"f\":\"uri\",\"d\":\"The publicly reachable HTTPS URL to deliver events to\"},\"scopes\":{\"t\":\"array\",\"d\":\"Which notes to receive events for.\",\"i\":{\"t\":\"string\",\"e\":[\"personal\",\"public\",\"workspace\"]}},\"events\":{\"t\":\"array\",\"d\":\"Event names to subscribe to.\",\"i\":{\"$\":2}},\"folder_ids\":{\"t\":\"array\",\"d\":\"Restrict delivery to notes in these folders or any of their subfolders.\",\"i\":{\"t\":\"string\"}}},\"r\":[\"url\",\"scopes\"]}]"], [[], "[\"application/json\",{\"t\":\"object\",\"p\":{\"url\":{\"t\":\"string\",\"f\":\"uri\",\"d\":\"The publicly reachable HTTPS URL to deliver events to.\"},\"scopes\":{\"t\":\"array\",\"d\":\"Which notes to receive events for; replaces the current scopes.\",\"i\":{\"t\":\"string\",\"e\":[\"personal\",\"public\",\"workspace\"]}},\"events\":{\"t\":\"array\",\"d\":\"Event names to subscribe to; replaces the current subscriptions.\",\"i\":{\"$\":2}},\"folder_ids\":{\"t\":\"array\",\"d\":\"Restrict delivery to notes in these folders or any of their subfolders; replaces the current folder filter.\",\"i\":{\"t\":\"string\"}},\"enabled\":{\"t\":\"boolean\",\"d\":\"Pause (`false`) or resume (`true`) event deliveries.\"}}}]"], 0] });
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { safe, redact, refuse } from "../_shared/rest/value-safety.js";
|
|
2
|
+
import { openapi } from "./openapi.generated.js";
|
|
3
|
+
export const READS = new Set([
|
|
4
|
+
"GET /v1/notes",
|
|
5
|
+
"GET /v1/notes/{note_id}",
|
|
6
|
+
"GET /v1/notes/{note_id}/transcript",
|
|
7
|
+
"GET /v1/folders",
|
|
8
|
+
"GET /v1/spaces",
|
|
9
|
+
]);
|
|
10
|
+
export const GRANOLA_VALUE_SAFETY = {
|
|
11
|
+
title: "Granola",
|
|
12
|
+
operations: Object.fromEntries(openapi.ops.map(([method, path]) => {
|
|
13
|
+
const key = `${method} ${path}`;
|
|
14
|
+
return [
|
|
15
|
+
key,
|
|
16
|
+
!READS.has(key)
|
|
17
|
+
? refuse("Only notes, note details, transcripts, folders and spaces are reviewed. Audit, legal holds, webhook management and writes are unavailable.")
|
|
18
|
+
: key === "GET /v1/notes/{note_id}"
|
|
19
|
+
? redact("Photo URLs grant unauthenticated access and are always withheld. Private handwritten note content (vendor-null for workspace keys) and space memberships describe API-key visibility, not credentials; their nested values still cross value safety.", ["attachments[].url"], ["private_notes_text", "private_notes_markdown", "space_membership"])
|
|
20
|
+
: safe("Read-only meeting content and identifiers; no API credential is returned. Vendor error prose stays withheld."),
|
|
21
|
+
];
|
|
22
|
+
})),
|
|
23
|
+
embeddedUrls: {
|
|
24
|
+
operations: {
|
|
25
|
+
"GET /v1/notes": ["notes[].title"],
|
|
26
|
+
"GET /v1/notes/{note_id}": [
|
|
27
|
+
"title",
|
|
28
|
+
"summary_text",
|
|
29
|
+
"summary_markdown",
|
|
30
|
+
"private_notes_text",
|
|
31
|
+
"private_notes_markdown",
|
|
32
|
+
"transcript[].text",
|
|
33
|
+
],
|
|
34
|
+
"GET /v1/notes/{note_id}/transcript": ["transcript[].text"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Generated by scripts/generate-providers.mjs; do not edit.
|
|
2
|
+
export const skill = {
|
|
3
|
+
"content": "## Accessible meeting records\n\nWorkspace keys reach public workspace notes and spaces explicitly enabled for\nGranola API access. Folder permissions inherit their space's setting. They do\nnot grant unrelated private notes. Only notes with a generated AI summary and\ntranscript appear; a missing result does not establish that a meeting never\nhappened. The owner's private handwritten notes are null for workspace keys.\n\nDiscover actual note, folder and space IDs. API IDs use not_, fol_ and spa_\nprefixes; a web-link UUID, title or human name is not an API ID. Notes can\nbelong to several folders and spaces; folder parent_folder_id describes the\nhierarchy and space_id joins a folder to its space. Folder filtering includes\ndescendant folders. Null space_id can also mean an inaccessible space.\n\nOperation search searches the API contract, not meeting content. The REST API\noffers date, folder and space filters, with no natural-language meeting search.\nFilter accessible notes and inspect their contents to answer a topic question;\ndisclose the period and scope examined.\n\n## Transcript interpretation\n\nInline transcripts are optional. If including one fails with HTTP 413\n(TRANSCRIPT_TOO_LARGE), read note details without include and retrieve the\nseparate transcript endpoint through its complete continuation sequence. No\npartial transcript proves a complete quotation or count. An empty page can\nstill have a continuation; follow the returned paging state.\n\nSpeaker source describes the audio input, not a person's identity. macOS may\ndistinguish microphone from speaker audio. iOS currently uses microphone and\nmay supply anonymous diarization labels; do not invent attendee attribution.\nPreserve start/end timestamps when quoting. Summaries are AI-generated, so\nconfirm important wording against the transcript. Return only the relevant\npassages and identifying meeting metadata.\n\n## Conventions\n\n- **Paging:** while `page.hasMore` is true, continue with `page.nextCursor` unchanged. For `granola_api_details`, `granola_api_read`, `granola_api_search`, pass it as the top-level `cursor`; omit raw paging anchors on continuations. Keep the original method, path, filters, page size, fields and headers. The last page has no `nextCursor`. If `page.hasMore` is true without a cursor, the data is incomplete and continuation was withheld. When approaching execute_code's per-program call limit, return page.nextCursor before the call that would exceed it and continue in the next program; don't restart.\n- **Results:** `fields` narrows a read's result to the dot paths you name. `truncated` lists the dot paths a result cut for size; the ids that fetch what was cut stay in the result.\n- **Hatch:** `granola_api_search` finds this API's operations and reads no account data, `granola_api_details` shows one operation's contract, and `granola_api_read` calls a reviewed read. An operation without a reviewed verdict is refused. 13 operations are refused by design; use `granola_api_details` with the method and path to inspect the original operation summary and its separate refusal. Use details for refused methods outside the read/write call schemas; those methods remain unavailable.\n- **Writes:** a write is sent once and never retried. When its outcome is unknown, read the target before repeating it. Writes take no idempotency key.\n- **Errors:** codes come from the vendor's statuses and error facts, never its prose. An unknown id, or one these credentials cannot reach, fails as `connector_call_failed`; this API does not tell the two apart. It documents no conflict.\n- **Withheld values:** a value withheld for safety reads `\"[redacted]\"`. Writes refuse whole marker values and withheld links; longer prose may mention the marker. Paths and queries refuse any containment.",
|
|
4
|
+
"instructionsHeading": "Workspace instructions"
|
|
5
|
+
};
|
|
@@ -314,7 +314,7 @@ export const getContributions = get("getContributions", "/contributions", {
|
|
|
314
314
|
},
|
|
315
315
|
success: GetContributionsResponse,
|
|
316
316
|
}).annotate(Summary, "List contributions (gifts), filtered by campaign, campus, status and dates");
|
|
317
|
-
|
|
317
|
+
const chargeCashPaymentMethod = post("chargeCashPaymentMethod", "/contributions", {
|
|
318
318
|
payload: ChargeCashPaymentMethodParams,
|
|
319
319
|
success: ChargeCashPaymentMethodResponse,
|
|
320
320
|
}).annotate(Summary, "Charge a donor's saved card, bank or wallet payment method for a cash gift");
|
|
@@ -322,7 +322,7 @@ export const getContributionById = get("getContributionById", "/contributions/:c
|
|
|
322
322
|
params: { "contributionId": Schema.String.pipe(Schema.annotateKey({ description: "contribution id." })) },
|
|
323
323
|
success: GetContributionResponse,
|
|
324
324
|
}).annotate(Summary, "Retrieve one contribution (gift) and its donor, campaign and settlement status");
|
|
325
|
-
|
|
325
|
+
const initiateRefund = post("initiateRefund", "/contributions/:contributionId/initiate-refund", {
|
|
326
326
|
params: {
|
|
327
327
|
"contributionId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the contribution to refund." })),
|
|
328
328
|
},
|
|
@@ -372,7 +372,7 @@ export const getRefunds = get("getRefunds", "/refunds", {
|
|
|
372
372
|
},
|
|
373
373
|
success: GetRefundsResponse,
|
|
374
374
|
}).annotate(Summary, "List refunds with contribution and deposit joins and cent amounts");
|
|
375
|
-
|
|
375
|
+
const getRefundById = get("getRefundById", "/refunds/:refundId", {
|
|
376
376
|
params: { "refundId": Schema.String.pipe(Schema.annotateKey({ description: "refund id." })) },
|
|
377
377
|
success: GetRefundResponse,
|
|
378
378
|
}).annotate(Summary, "Retrieve one refund and its contribution, status and cent amounts");
|
|
@@ -387,11 +387,11 @@ export const getChargebacks = get("getChargebacks", "/chargebacks", {
|
|
|
387
387
|
},
|
|
388
388
|
success: GetChargebacksResponse,
|
|
389
389
|
}).annotate(Summary, "List card disputes and ACH returns (chargebacks)");
|
|
390
|
-
|
|
390
|
+
const getChargebackById = get("getChargebackById", "/chargebacks/:chargebackId", {
|
|
391
391
|
params: { "chargebackId": Schema.String.pipe(Schema.annotateKey({ description: "chargeback id." })) },
|
|
392
392
|
success: GetChargebackResponse,
|
|
393
393
|
}).annotate(Summary, "Retrieve one chargeback (card dispute or ACH return)");
|
|
394
|
-
|
|
394
|
+
const getWebhooks = get("getWebhooks", "/webhooks", {
|
|
395
395
|
query: {
|
|
396
396
|
"limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100))).pipe(Schema.annotateKey({ description: "The number of webhook subscriptions to return." })),
|
|
397
397
|
"page": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1))).pipe(Schema.annotateKey({ description: "The page number of webhook subscriptions to return." })),
|
|
@@ -402,11 +402,11 @@ export const getWebhooks = get("getWebhooks", "/webhooks", {
|
|
|
402
402
|
},
|
|
403
403
|
success: GetWebhooksResponse,
|
|
404
404
|
}).annotate(Summary, "List webhook subscriptions, destinations and enabled event names");
|
|
405
|
-
|
|
405
|
+
const getWebhookById = get("getWebhookById", "/webhooks/:webhookId", {
|
|
406
406
|
params: { "webhookId": Schema.String.pipe(Schema.annotateKey({ description: "webhook id." })) },
|
|
407
407
|
success: GetWebhookResponse,
|
|
408
408
|
}).annotate(Summary, "Retrieve one webhook subscription and its delivery destination");
|
|
409
|
-
|
|
409
|
+
const getWebhookEventLogs = get("getWebhookEventLogs", "/webhooks/:webhookId/event-logs", {
|
|
410
410
|
params: { "webhookId": Schema.String.pipe(Schema.annotateKey({ description: "webhook id." })) },
|
|
411
411
|
query: {
|
|
412
412
|
"limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(50))).pipe(Schema.annotateKey({ description: "The number of webhook event logs to return." })),
|
|
@@ -467,7 +467,7 @@ export const getDonorProfiles = get("getDonorProfiles", "/donors", {
|
|
|
467
467
|
},
|
|
468
468
|
success: GetDonorsResponse,
|
|
469
469
|
}).annotate(Summary, "List donor profiles, contact information and giving counts");
|
|
470
|
-
|
|
470
|
+
const createDonorProfile = post("createDonorProfile", "/donors", {
|
|
471
471
|
payload: CreateDonorProfileParams,
|
|
472
472
|
success: CreateDonorProfileResponse,
|
|
473
473
|
}).annotate(Summary, "Create a donor profile for a nonprofit");
|
|
@@ -475,12 +475,12 @@ export const getDonorProfile = get("getDonorProfile", "/donors/:donorId", {
|
|
|
475
475
|
params: { "donorId": Schema.String.pipe(Schema.annotateKey({ description: "The ID of the donor to fetch." })) },
|
|
476
476
|
success: GetDonorResponse,
|
|
477
477
|
}).annotate(Summary, "Retrieve a donor profile, phone, address and giving counts");
|
|
478
|
-
|
|
478
|
+
const updateDonorProfile = patch("updateDonorProfile", "/donors/:donorId", {
|
|
479
479
|
params: { "donorId": Schema.String.pipe(Schema.annotateKey({ description: "The ID of the donor to update." })) },
|
|
480
480
|
payload: UpdateDonorProfileParams,
|
|
481
481
|
success: Schema.Unknown,
|
|
482
482
|
}).annotate(Summary, "Update (edit) a donor's name, email or phone");
|
|
483
|
-
|
|
483
|
+
const createSubscription = post("createSubscription", "/subscriptions/:donorId", {
|
|
484
484
|
params: {
|
|
485
485
|
"donorId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the donor to create the subscription for." })),
|
|
486
486
|
},
|
|
@@ -526,14 +526,14 @@ export const getSubscriptions = get("getSubscriptions", "/subscriptions", {
|
|
|
526
526
|
},
|
|
527
527
|
success: GetSubscriptionsResponse,
|
|
528
528
|
}).annotate(Summary, "List recurring gifts (subscriptions) across donors");
|
|
529
|
-
|
|
529
|
+
const getSubscriptionById = get("getSubscriptionById", "/subscriptions/:donorId/:subscriptionId", {
|
|
530
530
|
params: {
|
|
531
531
|
"donorId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the donor to get the subscription for." })),
|
|
532
532
|
"subscriptionId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the subscription to get." })),
|
|
533
533
|
},
|
|
534
534
|
success: GetSubscriptionResponse,
|
|
535
535
|
}).annotate(Summary, "Retrieve one donor's recurring gift and payment schedule");
|
|
536
|
-
|
|
536
|
+
const updateSubscriptionById = patch("updateSubscriptionById", "/subscriptions/:donorId/:subscriptionId", {
|
|
537
537
|
params: {
|
|
538
538
|
"donorId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the donor to update the subscription for." })),
|
|
539
539
|
"subscriptionId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the subscription to update." })),
|
|
@@ -541,7 +541,7 @@ export const updateSubscriptionById = patch("updateSubscriptionById", "/subscrip
|
|
|
541
541
|
payload: UpdateSubscriptionRequest,
|
|
542
542
|
success: Schema.Unknown,
|
|
543
543
|
}).annotate(Summary, "Update a recurring gift, payment method, schedule or dollar amount");
|
|
544
|
-
|
|
544
|
+
const cancelSubscription = del("cancelSubscription", "/subscriptions/:donorId/:subscriptionId", {
|
|
545
545
|
params: {
|
|
546
546
|
"donorId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the donor to cancel the subscription for." })),
|
|
547
547
|
"subscriptionId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the subscription to cancel." })),
|
|
@@ -584,7 +584,7 @@ export const getCampaigns = get("getCampaigns", "/campaigns", {
|
|
|
584
584
|
},
|
|
585
585
|
success: GetCampaignsResponse,
|
|
586
586
|
}).annotate(Summary, "List campaigns and subcampaigns, with parent and giving relationships");
|
|
587
|
-
|
|
587
|
+
const getCampaignById = get("getCampaignById", "/campaigns/:campaignId", {
|
|
588
588
|
params: {
|
|
589
589
|
"campaignId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the campaign/subcampaign to get." })),
|
|
590
590
|
},
|
|
@@ -600,15 +600,15 @@ export const getLocations = get("getLocations", "/locations", {
|
|
|
600
600
|
},
|
|
601
601
|
success: GetLocationsResponse,
|
|
602
602
|
}).annotate(Summary, "List nonprofit locations (campuses)");
|
|
603
|
-
|
|
603
|
+
const getLocationById = get("getLocationById", "/locations/:locationId", {
|
|
604
604
|
params: { "locationId": Schema.String.pipe(Schema.annotateKey({ description: "The Id of the location to get." })) },
|
|
605
605
|
success: GetLocationByIdResponse,
|
|
606
606
|
}).annotate(Summary, "Retrieve a nonprofit location (campus)");
|
|
607
|
-
|
|
607
|
+
const authorizePayment = post("authorizePayment", "/payments/authorize", {
|
|
608
608
|
payload: AuthorizePaymentRequest,
|
|
609
609
|
success: AuthorizePaymentResponse,
|
|
610
610
|
}).annotate(Summary, "Authorize a payment, or save a payment method with zero amountInCents");
|
|
611
|
-
|
|
611
|
+
const getDestinations = get("getDestinations", "/tap/destinations", {
|
|
612
612
|
query: {
|
|
613
613
|
"limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100))).pipe(Schema.annotateKey({ description: "The number of tap destinations to return." })),
|
|
614
614
|
"page": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1))).pipe(Schema.annotateKey({ description: "The page number of the tap destinations to return." })),
|
|
@@ -620,13 +620,13 @@ export const getDestinations = get("getDestinations", "/tap/destinations", {
|
|
|
620
620
|
},
|
|
621
621
|
success: GetTapDestinationsResponse,
|
|
622
622
|
}).annotate(Summary, "List Tap-to-give destinations and configured URLs or messages");
|
|
623
|
-
|
|
623
|
+
const getDestinationById = get("getDestinationById", "/tap/destinations/:destinationId", {
|
|
624
624
|
params: {
|
|
625
625
|
"destinationId": Schema.String.pipe(Schema.annotateKey({ description: "The ID of the tap destination." })),
|
|
626
626
|
},
|
|
627
627
|
success: GetTapDestinationByIdResponse,
|
|
628
628
|
}).annotate(Summary, "Retrieve a Tap-to-give destination URL or message");
|
|
629
|
-
|
|
629
|
+
const getDevices = get("getDevices", "/tap/devices", {
|
|
630
630
|
query: {
|
|
631
631
|
"limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100))).pipe(Schema.annotateKey({ description: "The number of tap devices to return." })),
|
|
632
632
|
"page": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1))).pipe(Schema.annotateKey({ description: "The page number of the tap devices to return." })),
|
|
@@ -657,11 +657,11 @@ export const getDevices = get("getDevices", "/tap/devices", {
|
|
|
657
657
|
},
|
|
658
658
|
success: GetTapDevicesResponse,
|
|
659
659
|
}).annotate(Summary, "List Tap-to-give devices, groups and current destinations");
|
|
660
|
-
|
|
660
|
+
const getDeviceById = get("getDeviceById", "/tap/devices/:deviceId", {
|
|
661
661
|
params: { "deviceId": Schema.String.pipe(Schema.annotateKey({ description: "The ID of the tap device." })) },
|
|
662
662
|
success: GetTapDeviceByIdResponse,
|
|
663
663
|
}).annotate(Summary, "Retrieve a Tap-to-give device and its current destination");
|
|
664
|
-
|
|
664
|
+
const getEvents = get("getEvents", "/tap/events", {
|
|
665
665
|
query: {
|
|
666
666
|
"limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100))).pipe(Schema.annotateKey({ description: "The number of tap events to return." })),
|
|
667
667
|
"page": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1))).pipe(Schema.annotateKey({ description: "The page number of the tap events to return." })),
|
|
@@ -682,7 +682,7 @@ export const getEvents = get("getEvents", "/tap/events", {
|
|
|
682
682
|
},
|
|
683
683
|
success: GetTapEventsResponse,
|
|
684
684
|
}).annotate(Summary, "List Tap-to-give engagement events (taps, not donations)");
|
|
685
|
-
|
|
685
|
+
const getGroups = get("getGroups", "/tap/groups", {
|
|
686
686
|
query: {
|
|
687
687
|
"limit": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(100))).pipe(Schema.annotateKey({ description: "The number of tap groups to return." })),
|
|
688
688
|
"page": Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(1))).pipe(Schema.annotateKey({ description: "The page number of the tap groups to return." })),
|
|
@@ -693,11 +693,11 @@ export const getGroups = get("getGroups", "/tap/groups", {
|
|
|
693
693
|
},
|
|
694
694
|
success: GetTapGroupsResponse,
|
|
695
695
|
}).annotate(Summary, "List Tap-to-give device groups");
|
|
696
|
-
|
|
696
|
+
const getGroupById = get("getGroupById", "/tap/groups/:groupId", {
|
|
697
697
|
params: { "groupId": Schema.String.pipe(Schema.annotateKey({ description: "The ID of the tap group." })) },
|
|
698
698
|
success: GetTapGroupByIdResponse,
|
|
699
699
|
}).annotate(Summary, "Retrieve a Tap-to-give group and its devices and destination");
|
|
700
|
-
|
|
700
|
+
const health = get("health", "/health", {
|
|
701
701
|
success: Schema.String,
|
|
702
702
|
}).annotate(Summary, "Read the API health text (not an authenticated credential check)");
|
|
703
703
|
export const OverflowApi = {
|
|
@@ -808,7 +808,7 @@ function pageNumber(value) {
|
|
|
808
808
|
throw new ConnectorCallError("invalid_args", "This Overflow cursor contains no valid page number.");
|
|
809
809
|
return page;
|
|
810
810
|
}
|
|
811
|
-
|
|
811
|
+
const overflowPage = {
|
|
812
812
|
param: "page",
|
|
813
813
|
in: "query",
|
|
814
814
|
decode: pageNumber,
|
|
@@ -823,7 +823,7 @@ export const overflowPage = {
|
|
|
823
823
|
return more ? current + 1 : null;
|
|
824
824
|
},
|
|
825
825
|
};
|
|
826
|
-
|
|
826
|
+
const overflowEventPage = {
|
|
827
827
|
param: "cursor",
|
|
828
828
|
in: "query",
|
|
829
829
|
decode(cursor) {
|
|
@@ -13,7 +13,7 @@ import { TABLE } from "./safety.js";
|
|
|
13
13
|
import { excludedApps, series } from "./lib/guide.js";
|
|
14
14
|
import { asRecord, asArray, resources, attribute, pageOf } from "./lib/json-api.js";
|
|
15
15
|
import { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS, DEFAULT_USER_AGENT, } from "./lib/constants.js";
|
|
16
|
-
export { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, PLANNING_CENTER_MAX_RESPONSE_BYTES, PLANNING_CENTER_ADMISSION,
|
|
16
|
+
export { PLANNING_CENTER_API_BASE_URL, PLANNING_CENTER_APPS, PLANNING_CENTER_API_VERSIONS, MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, PLANNING_CENTER_MAX_RESPONSE_BYTES, PLANNING_CENTER_ADMISSION, } from "./lib/constants.js";
|
|
17
17
|
export const CREDENTIAL = {
|
|
18
18
|
label: "Planning Center personal access token",
|
|
19
19
|
description: "Create one at api.planningcenteronline.com/oauth/applications → Personal Access Tokens. It acts with the permissions of the user who creates it, in every product that user can open, so create it as a user whose access matches this connection's purpose.",
|
|
@@ -59,7 +59,7 @@ export function isApp(value) {
|
|
|
59
59
|
* normalization the guarded transport applies — so `..` cannot carry a request
|
|
60
60
|
* from `/people/v2` to `/oauth` or anywhere else outside a product.
|
|
61
61
|
*/
|
|
62
|
-
|
|
62
|
+
function appForPath(path, apps) {
|
|
63
63
|
let normalized;
|
|
64
64
|
if (path.startsWith("/") && !path.startsWith("//")) {
|
|
65
65
|
try {
|
|
@@ -269,7 +269,7 @@ export const errors = {
|
|
|
269
269
|
/** All ten product pins retain their own public URL and version. */
|
|
270
270
|
export const PCO_PREFIX = "pco";
|
|
271
271
|
export const PCO_INDEX = new PlanningCenterIndex(openapi);
|
|
272
|
-
|
|
272
|
+
const getPeopleMe = get("getPeopleMe", "/people/v2/me", {
|
|
273
273
|
success: Schema.Unknown,
|
|
274
274
|
query: { include: Schema.String },
|
|
275
275
|
headers: { "X-PCO-API-Version": Schema.String },
|
|
@@ -317,7 +317,7 @@ export const PCO_NAMED = {
|
|
|
317
317
|
"POST /people/v2/people/{person_id}/workflow_cards/{workflow_card_id}/restore": "apply_workflow_card_action",
|
|
318
318
|
};
|
|
319
319
|
/** Native collection offset metadata, read only after response safety. */
|
|
320
|
-
|
|
320
|
+
const pcoPage = {
|
|
321
321
|
param: "offset",
|
|
322
322
|
in: "query",
|
|
323
323
|
total: (data) => pageOf(asRecord(data)).totalCount ?? undefined,
|
|
@@ -55,4 +55,4 @@ export declare const PLANNING_CENTER_MAX_RESPONSE_BYTES: number;
|
|
|
55
55
|
*/
|
|
56
56
|
export declare const PLANNING_CENTER_ADMISSION: ConnectorCallAdmissionPolicy;
|
|
57
57
|
/** Planning Center refuses a request without a descriptive one (HTTP 403). */
|
|
58
|
-
export declare const DEFAULT_USER_AGENT = "connecta/0.36.
|
|
58
|
+
export declare const DEFAULT_USER_AGENT = "connecta/0.36.1 (+https://github.com/zackbart/connecta)";
|
|
@@ -8,7 +8,7 @@ export function asArray(value) {
|
|
|
8
8
|
export function compact(value) {
|
|
9
9
|
return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
function isResource(value) {
|
|
12
12
|
const record = asRecord(value);
|
|
13
13
|
return typeof record["id"] === "string" && typeof record["type"] === "string";
|
|
14
14
|
}
|
|
@@ -22,7 +22,7 @@ export function indexIncluded(document) {
|
|
|
22
22
|
}
|
|
23
23
|
return index;
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
function relationshipData(resource, name) {
|
|
26
26
|
return resource.relationships?.[name]?.data;
|
|
27
27
|
}
|
|
28
28
|
/** The related id, `null` when Planning Center says there is none. */
|
|
@@ -123,7 +123,7 @@ function pick(value, keys) {
|
|
|
123
123
|
return Object.fromEntries(keys.flatMap((key) => source[key] === undefined || scalar(source[key]) === undefined ? [] : [[key, scalar(source[key])]]));
|
|
124
124
|
}
|
|
125
125
|
/** Closed accounting projection: unknown fields never become a way around privacy. SyncToken is a version, not a credential. */
|
|
126
|
-
|
|
126
|
+
function safeRecord(value) {
|
|
127
127
|
const source = object(value), out = pick(source, scalars);
|
|
128
128
|
for (const key of references)
|
|
129
129
|
if (source[key])
|
|
@@ -15,7 +15,7 @@ export const SHEETS = {
|
|
|
15
15
|
};
|
|
16
16
|
const SpreadsheetId = { spreadsheetId: Schema.String };
|
|
17
17
|
const optionalString = Schema.optionalKey(Schema.String);
|
|
18
|
-
|
|
18
|
+
const getSpreadsheet = get("getSpreadsheet", "/spreadsheets/:spreadsheetId", {
|
|
19
19
|
params: SpreadsheetId,
|
|
20
20
|
query: { includeGridData: optionalString, fields: optionalString },
|
|
21
21
|
success: Schema.Unknown,
|
|
@@ -23,7 +23,7 @@ export const sheetsIndex = new OperationIndex(sheetsDiscovery, {
|
|
|
23
23
|
pathMatching: "uri-template",
|
|
24
24
|
});
|
|
25
25
|
/** Decode range colons only. Encoded actions, ids and unreserved bytes still fail the shared backstop. */
|
|
26
|
-
|
|
26
|
+
function sheetsPath(path) {
|
|
27
27
|
const match = /^(\/spreadsheets\/[A-Za-z0-9_-]+\/values\/)([^/?#]+)$/.exec(path);
|
|
28
28
|
if (!match)
|
|
29
29
|
return path;
|
|
@@ -37,7 +37,7 @@ export function sheetsPath(path) {
|
|
|
37
37
|
}
|
|
38
38
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
39
39
|
/** Same scalar, range and write-cell ceilings as the named workflows, including filter-selected writes. */
|
|
40
|
-
|
|
40
|
+
function sheetsRequestProblem(call) {
|
|
41
41
|
const body = record(call.body);
|
|
42
42
|
if (call.op.path === "/spreadsheets/{spreadsheetId}:batchUpdate") {
|
|
43
43
|
const requests = body["requests"];
|
|
@@ -23,7 +23,7 @@ const ReadQuery = {
|
|
|
23
23
|
commentsViewMode: Schema.optionalKey(Schema.String),
|
|
24
24
|
};
|
|
25
25
|
// Discovery calls this one parameter `presentationsId` (plural).
|
|
26
|
-
|
|
26
|
+
const getPresentation = get("getPresentation", "/presentations/:presentationsId", {
|
|
27
27
|
params: { presentationsId: Schema.String },
|
|
28
28
|
query: ReadQuery,
|
|
29
29
|
success: Schema.Unknown,
|
|
@@ -115,7 +115,7 @@ export const READ_CAP_MS = 60_000;
|
|
|
115
115
|
* /v1/tax/calculations` is deliberately absent: it persists a Calculation
|
|
116
116
|
* object a later Tax Transaction can reference, and Stripe bills each call.
|
|
117
117
|
*/
|
|
118
|
-
|
|
118
|
+
const READ_POSTS = [
|
|
119
119
|
["POST", "/v1/invoices/create_preview", "Computes an upcoming invoice; Stripe persists nothing."],
|
|
120
120
|
];
|
|
121
121
|
function invalid(message) {
|
|
@@ -126,7 +126,7 @@ function invalid(message) {
|
|
|
126
126
|
* The key's own prefix carries its mode, so a live key in a sandbox
|
|
127
127
|
* connector (or the reverse) is refused before any request leaves.
|
|
128
128
|
*/
|
|
129
|
-
|
|
129
|
+
function stripeKeyProblem(key, mode) {
|
|
130
130
|
if (/^(?:sk|rk)_org_/.test(key)) {
|
|
131
131
|
return "This is a Stripe organization key, which needs a Stripe-Context header this connector does not send. Use one account's secret or restricted key.";
|
|
132
132
|
}
|
|
@@ -213,7 +213,7 @@ function expansions(call) {
|
|
|
213
213
|
* secret's value, or a full Issuing card number or CVC, which an Issuing
|
|
214
214
|
* authorization, transaction, or token can embed as well as the card itself.
|
|
215
215
|
*/
|
|
216
|
-
|
|
216
|
+
function refuse(call) {
|
|
217
217
|
const expanded = expansions(call);
|
|
218
218
|
if (/^\/v1\/apps\/secrets(?:\/|$)/.test(call.path) && expanded.some((path) => /(?:^|\.)payload$/.test(path))) {
|
|
219
219
|
return "Stripe Apps secret values are not returned through Connecta. Read the secret's metadata without expanding payload.";
|
|
@@ -225,7 +225,7 @@ export function refuse(call) {
|
|
|
225
225
|
return undefined;
|
|
226
226
|
}
|
|
227
227
|
export const stripeIndex = new OperationIndex(openapi, { vendor: "stripe", title: "Stripe" });
|
|
228
|
-
|
|
228
|
+
const stripeSafety = valueSafety(STRIPE_VALUE_SAFETY, () => stripeIndex);
|
|
229
229
|
function operationRefusal(op) {
|
|
230
230
|
return (checkServer(op.server) ??
|
|
231
231
|
stripeSafety.refusal(op.method, op.path) ??
|
|
@@ -268,11 +268,11 @@ export const safety = () => ({
|
|
|
268
268
|
reviewed: restSafety(stripeSafety, stripeIndex),
|
|
269
269
|
sensitiveFields: { names: [], choice: "drop" },
|
|
270
270
|
});
|
|
271
|
-
|
|
271
|
+
const getAccount = get("getAccount", "/v1/account", {
|
|
272
272
|
headers: Schema.Struct({ "Stripe-Account": Schema.optionalKey(Schema.String) }),
|
|
273
273
|
success: Schema.Unknown,
|
|
274
274
|
});
|
|
275
|
-
|
|
275
|
+
const getBalance = get("getBalance", "/v1/balance", { success: Schema.Unknown });
|
|
276
276
|
const RESPONSE_MEDIA = { "GET /v1/quotes/{quote}/pdf": ["application/pdf"] };
|
|
277
277
|
export const StripeApi = {
|
|
278
278
|
...indexedVendor("stripe", stripeIndex, [getAccount, getBalance]),
|
|
@@ -317,7 +317,7 @@ export function connectedAccount(options) {
|
|
|
317
317
|
throw new Error('stripe() connectedAccount must be a Stripe account id ("acct_...").');
|
|
318
318
|
return account;
|
|
319
319
|
}
|
|
320
|
-
|
|
320
|
+
function checkServer(server) {
|
|
321
321
|
if (server === undefined || server === FILES_ORIGIN)
|
|
322
322
|
return undefined;
|
|
323
323
|
return server === METER_EVENTS_ORIGIN
|
|
@@ -162,7 +162,7 @@ export const listOrganizations = get("listOrganizations", "/organizations-list",
|
|
|
162
162
|
query: PageQuery,
|
|
163
163
|
success: Rows(OrganizationRecord),
|
|
164
164
|
}).annotate(Summary, "List all organizations these keys manage");
|
|
165
|
-
|
|
165
|
+
const getOrganization = get("getOrganization", "/organizations/:organization_id", {
|
|
166
166
|
params: id("organization_id"),
|
|
167
167
|
success: Single("organization_id", OrganizationRecord),
|
|
168
168
|
}).annotate(Summary, "Retrieve an organization");
|
|
@@ -174,7 +174,7 @@ export const listAllAccounts = get("listAllAccounts", "/accounts-list-all", {
|
|
|
174
174
|
query: PageQuery,
|
|
175
175
|
success: Rows(AccountRecord),
|
|
176
176
|
}).annotate(Summary, "List all donor accounts");
|
|
177
|
-
|
|
177
|
+
const getAccount = get("getAccount", "/accounts/:account_id", {
|
|
178
178
|
params: id("account_id"),
|
|
179
179
|
success: Single("account_id", AccountRecord),
|
|
180
180
|
}).annotate(Summary, "Retrieve a donor account");
|