{"openapi":"3.1.0","info":{"title":"Together API","version":"v1","description":"REST API for the Together fundraising revenue engine. Authenticate with an API key from `/settings/api`; the key identifies both caller and organisation. See [/developer/api](/developer/api) for the full reference."},"servers":[{"url":"/","description":"Current host"}],"security":[{"apiKey":[]}],"tags":[{"name":"meta","description":"Health and identity checks"},{"name":"organisations","description":"Organisation self-service"},{"name":"donors","description":"Donor records"},{"name":"donations","description":"Donation records"},{"name":"forms","description":"Donation forms"},{"name":"checkout-links","description":"One-off checkout links (/c/{code}) for pre-filled donations"},{"name":"recommendation-sets","description":"Recommendation sets"},{"name":"recipient-accounts","description":"Recipient-org Stripe accounts this organisation has linked via passphrase exchange. Use these as `recipient_org_account_link_id` when building recommendation-set allocations."},{"name":"introductions","description":"Programmatically introduce prospective recipient organisations to Together. Sends an invite email + tracks the prospect through pending → viewed → accepted → linked. Listen for `introduction.linked` to learn when the resulting `recipient_org_account_link_id` is usable. Raise+ entitlement."},{"name":"webhooks","description":"Outbound webhook events we POST to receiver endpoints. See /developer/webhooks for delivery semantics (signing, retries, idempotency)."}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key from /settings/api. Shown once at creation; store it securely. The key identifies both the caller and the organisation. Example: `Bearer pc_...`."}},"schemas":{"ErrorEnvelope":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","example":"https://alltogether.giving/developer/api/errors/not_found"},"code":{"type":"string","enum":["invalid_request","validation_failed","unauthorized","forbidden","not_found","method_not_allowed","conflict","idempotency_conflict","rate_limited","internal_error"],"example":"not_found"},"message":{"type":"string","example":"Donor not found"},"param":{"type":"string","example":"email","description":"Set on validation_failed responses; points at the offending field."},"request_id":{"type":"string","example":"req_abc123"},"doc_url":{"type":"string","example":"https://alltogether.giving/developer/api/errors/not_found"}},"required":["type","code","message","request_id"]}},"required":["error"]},"RecommendationSet":{"type":"object","properties":{"id":{"type":"string","example":"cm3rec00001abc123xyz456"},"name":{"type":"string","example":"Climate split — March 2026"},"slug":{"type":"string","example":"climate-split-march-2026"},"is_active":{"type":"boolean","example":true},"archived_at":{"type":["string","null"],"example":null},"expires_at":{"type":["string","null"],"example":null},"total_amount_cents":{"type":"integer","example":50000},"allocation_count":{"type":"integer","example":2},"donor_prefill_email":{"type":["string","null"],"example":"alice@example.com"},"created_at":{"type":"string","example":"2026-03-01T00:00:00.000Z"}},"required":["id","name","slug","is_active","archived_at","expires_at","total_amount_cents","allocation_count","donor_prefill_email","created_at"]},"RecommendationSetDetail":{"allOf":[{"$ref":"#/components/schemas/RecommendationSet"},{"type":"object","properties":{"url_key":{"type":"string","example":"a1b2c3d4..."},"description":{"type":["string","null"],"example":null},"donor_prefill":{"type":["object","null"],"additionalProperties":{"type":"string"},"example":{"email":"alice@example.com"}},"allocations":{"type":"array","items":{"$ref":"#/components/schemas/RecommendationAllocation"}}},"required":["url_key","description","donor_prefill","allocations"]}]},"RecommendationAllocation":{"type":"object","properties":{"id":{"type":"string","example":"cm3alc00001abc123xyz456"},"amount_cents":{"type":"integer","example":25000},"recipient_org_account_link_id":{"type":"string","example":"cm3prt00001abc123xyz456"},"link_label":{"type":"string","example":"Climate Action Lab"},"recipient_org_name":{"type":"string","example":"Australian Conservation Foundation"}},"required":["id","amount_cents","recipient_org_account_link_id","link_label","recipient_org_name"]},"Donor":{"type":"object","properties":{"id":{"type":"string","example":"cm3abcde0001abc123xyz456"},"donor_type":{"type":"string","enum":["INDIVIDUAL","ORGANISATION","TRUST","UNKNOWN"],"example":"INDIVIDUAL"},"first_name":{"type":["string","null"],"example":"Alice"},"last_name":{"type":["string","null"],"example":"Ng"},"organisation_name":{"type":["string","null"],"example":null},"email":{"type":["string","null"],"example":"alice@example.com"},"phone":{"type":["string","null"],"example":"0412345678"},"address_line1":{"type":["string","null"],"example":"42 Smith St"},"address_line2":{"type":["string","null"],"example":null},"suburb":{"type":["string","null"],"example":"Fitzroy"},"state":{"type":["string","null"],"example":"VIC"},"postcode":{"type":["string","null"],"example":"3065"},"country":{"type":"string","example":"AU"},"employer_name":{"type":["string","null"],"example":"Acme Pty Ltd"},"occupation":{"type":["string","null"],"example":"Engineer"},"created_at":{"type":"string","example":"2026-01-15T10:23:45.000Z"},"updated_at":{"type":"string","example":"2026-02-03T08:11:12.000Z"},"archived_at":{"type":["string","null"],"example":null}},"required":["id","donor_type","first_name","last_name","organisation_name","email","phone","address_line1","address_line2","suburb","state","postcode","country","employer_name","occupation","created_at","updated_at","archived_at"]},"Donation":{"type":"object","properties":{"id":{"type":"string","example":"cm3donat0001abc123xyz456"},"donor_id":{"type":"string","example":"cm3abcde0001abc123xyz456"},"external_id":{"type":["string","null"],"example":"pi_3abc..."},"source":{"type":"string","enum":["NATIONBUILDER","STRIPE","RAISELY","GATEWAY","CHECKOUT_LINK","SPLIT_GATEWAY","MANUAL","OTHER"],"example":"STRIPE"},"status":{"type":"string","enum":["PENDING","CONFIRMED","REFUNDED","FAILED"],"example":"CONFIRMED","description":"Lifecycle of the donation. PENDING covers any pre-settlement state - the Together-side row exists but funds have not cleared (Stripe call may not even have been made yet, or BECS / PayTo is in transit through the bank network). CONFIRMED is settled. REFUNDED is fully refunded. FAILED is a terminal failure (Stripe error at submit, card decline, BECS / PayTo dishonour, transfer failure). List endpoints default to status=CONFIRMED; pass `?status=PENDING` (or any other value) to override. To watch the lifecycle in real time, subscribe to the `donation.created`, `donation.processing`, `donation.succeeded`, and `donation.failed` webhook events."},"amount_cents":{"type":"integer","example":25000,"description":"Donor intent (gross) in minor units of `currency` - what the donor chose to give. For split-giving donations (`source: SPLIT_GATEWAY`) this is the donor's gross intent toward this recipient, NOT the post-fee amount that landed in the recipient's connected account. Platform fees are not exposed on this endpoint. To reconcile against bank deposits, see the corresponding `donation.*` webhook events."},"refunded_amount_cents":{"type":"integer","example":0},"currency":{"type":"string","example":"AUD"},"donation_date":{"type":"string","example":"2026-03-14T10:00:00.000Z"},"received_date":{"type":["string","null"],"example":"2026-03-14T10:00:04.000Z"},"payment_method":{"type":["string","null"],"example":"card"},"tracking_code":{"type":["string","null"],"example":"winter-2026"},"created_at":{"type":"string","example":"2026-03-14T10:00:00.000Z"},"updated_at":{"type":"string","example":"2026-03-14T10:00:00.000Z"},"deleted_at":{"type":["string","null"],"example":null}},"required":["id","donor_id","external_id","source","status","amount_cents","refunded_amount_cents","currency","donation_date","received_date","payment_method","tracking_code","created_at","updated_at","deleted_at"]},"DonationForm":{"type":"object","properties":{"id":{"type":"string","example":"cm3frm00001abc123xyz456"},"name":{"type":"string","example":"General donations"},"slug":{"type":"string","example":"general"},"status":{"type":"string","enum":["DRAFT","ACTIVE","PAUSED","ARCHIVED"],"example":"ACTIVE"},"title":{"type":"string","example":"Support our campaign"},"description":{"type":["string","null"],"example":null},"thank_you_message":{"type":["string","null"],"example":null},"suggested_amounts_cents":{"type":"array","items":{"type":"integer"},"example":[2500,5000,10000]},"allow_custom_amount":{"type":"boolean","example":true},"default_amount_cents":{"type":["integer","null"],"example":5000},"minimum_amount_cents":{"type":["integer","null"],"example":null},"maximum_amount_cents":{"type":["integer","null"],"example":null},"allow_recurring":{"type":"boolean","example":true},"recurring_frequencies":{"type":"array","items":{"type":"string","enum":["WEEKLY","FORTNIGHTLY","MONTHLY","QUARTERLY","ANNUALLY"]},"example":["MONTHLY"]},"address_visibility":{"type":"string","enum":["NONE","OPTIONAL","REQUIRED"],"example":"OPTIONAL"},"phone_visibility":{"type":"string","enum":["NONE","OPTIONAL","REQUIRED"],"example":"OPTIONAL"},"disclaimer_text":{"type":["string","null"],"example":null},"tracking_code":{"type":["string","null"],"example":null},"embed_token":{"type":["string","null"],"example":"v9XyZ_AbCdEfGhIjKlMnOpQr"},"embed_allowed_domains":{"type":"array","items":{"type":"string"},"example":["give.example.org"]},"created_at":{"type":"string","example":"2026-01-15T10:23:45.000Z"},"updated_at":{"type":"string","example":"2026-02-03T08:11:12.000Z"},"archived_at":{"type":["string","null"],"example":null}},"required":["id","name","slug","status","title","description","thank_you_message","suggested_amounts_cents","allow_custom_amount","default_amount_cents","minimum_amount_cents","maximum_amount_cents","allow_recurring","recurring_frequencies","address_visibility","phone_visibility","disclaimer_text","tracking_code","embed_token","embed_allowed_domains","created_at","updated_at","archived_at"]},"CheckoutLink":{"type":"object","properties":{"id":{"type":"string","example":"cm3chk00001abc123xyz456"},"code":{"type":"string","example":"aB3xQ_2wPo"},"donor_email":{"type":"string","example":"alice@example.com"},"donor_first_name":{"type":["string","null"],"example":"Alice"},"donor_last_name":{"type":["string","null"],"example":"Ng"},"donor_phone":{"type":["string","null"],"example":null},"amount_cents":{"type":"integer","example":10000},"currency":{"type":"string","example":"AUD"},"description":{"type":["string","null"],"example":"Annual pledge"},"status":{"type":"string","enum":["ACTIVE","COMPLETED","EXPIRED","CANCELED"],"example":"ACTIVE"},"source":{"type":"string","enum":["ADMIN","API"],"example":"API"},"tracking_code":{"type":["string","null"],"example":null},"payment_methods":{"type":"array","items":{"type":"string"},"example":["card","au_becs_debit","payto"]},"stripe_integration_id":{"type":["string","null"],"example":null},"donor_id":{"type":["string","null"],"example":null},"donation_id":{"type":["string","null"],"example":null},"visit_count":{"type":"integer","example":0},"last_visited_at":{"type":["string","null"],"example":null},"expires_at":{"type":["string","null"],"example":null},"created_at":{"type":"string","example":"2026-01-15T10:23:45.000Z"},"updated_at":{"type":"string","example":"2026-02-03T08:11:12.000Z"},"archived_at":{"type":["string","null"],"example":null}},"required":["id","code","donor_email","donor_first_name","donor_last_name","donor_phone","amount_cents","currency","description","status","source","tracking_code","payment_methods","stripe_integration_id","donor_id","donation_id","visit_count","last_visited_at","expires_at","created_at","updated_at","archived_at"]},"OrganisationMe":{"type":"object","properties":{"id":{"type":"string","example":"cm3org00001abc123xyz456"},"slug":{"type":"string","example":"qed"},"name":{"type":"string","example":"QED"},"is_sandbox":{"type":"boolean","example":false},"timezone":{"type":"string","example":"Australia/Sydney"},"default_currency":{"type":"string","example":"AUD"},"created_at":{"type":"string","example":"2026-01-01T00:00:00.000Z"}},"required":["id","slug","name","is_sandbox","timezone","default_currency","created_at"]},"OrganisationLinked":{"type":"object","properties":{"id":{"type":"string","example":"cm3org00001abc123xyz456"},"name":{"type":"string","example":"Australian Conservation Foundation"},"slug":{"type":"string","example":"acf"},"abn":{"type":["string","null"],"example":"11223344556"},"website_url":{"type":["string","null"],"example":"https://acf.org.au"},"is_sandbox":{"type":"boolean","example":false},"recipient_org_account_link_ids":{"type":"array","items":{"type":"string"},"example":["cm3lnk00001abc123xyz456"]}},"required":["id","name","slug","abn","website_url","is_sandbox","recipient_org_account_link_ids"]},"RecipientAccount":{"type":"object","properties":{"id":{"type":"string","example":"cm3lnk00001abc123xyz456"},"label":{"type":"string","example":"Climate200 federal account (March 2026)"},"recipient_org_name":{"type":"string","example":"Australian Conservation Foundation"},"recipient_org_slug":{"type":"string","example":"acf"},"recipient_account_label":{"type":"string","example":"ACF main donations"},"is_active":{"type":"boolean","example":true},"created_at":{"type":"string","example":"2026-03-01T00:00:00.000Z"}},"required":["id","label","recipient_org_name","recipient_org_slug","recipient_account_label","is_active","created_at"]},"RecipientAccountDetail":{"allOf":[{"$ref":"#/components/schemas/RecipientAccount"},{"type":"object","properties":{"recipient_integration_id":{"type":"string","example":"cm3int00001abc123xyz456"},"stripe_connect_account_id":{"type":"string","example":"acct_1ABC..."},"charges_enabled":{"type":"boolean","example":true}},"required":["recipient_integration_id","stripe_connect_account_id","charges_enabled"]}]},"Introduction":{"type":"object","properties":{"id":{"type":"string","example":"cm3intr0001abc123xyz456"},"status":{"type":"string","enum":["PENDING","VIEWED","ACCEPTED","LINKED","EXPIRED","REVOKED"],"example":"PENDING"},"invitee_email":{"type":"string","example":"alice@example.com"},"invitee_org_name_hint":{"type":["string","null"],"example":"Alice's Org"},"external_id":{"type":["string","null"],"example":"boost_123"},"invite_url":{"type":"string","example":"https://alltogether.giving/invite/aB3xQ..."},"expires_at":{"type":"string","example":"2026-04-26T00:00:00.000Z"},"viewed_at":{"type":["string","null"],"example":null},"accepted_at":{"type":["string","null"],"example":null},"linked_at":{"type":["string","null"],"example":null},"revoked_at":{"type":["string","null"],"example":null},"accepted_organisation_id":{"type":["string","null"],"example":null},"accepted_recipient_org_account_link_id":{"type":["string","null"],"example":null},"resend_count":{"type":"integer","example":0},"last_resent_at":{"type":["string","null"],"example":null},"created_at":{"type":"string","example":"2026-03-27T00:00:00.000Z"}},"required":["id","status","invitee_email","invitee_org_name_hint","external_id","invite_url","expires_at","viewed_at","accepted_at","linked_at","revoked_at","accepted_organisation_id","accepted_recipient_org_account_link_id","resend_count","last_resent_at","created_at"]}},"parameters":{}},"paths":{"/api/v1/health":{"get":{"tags":["meta"],"summary":"Liveness probe","description":"Returns 200 with build metadata. No auth required. Use for uptime checks and smoke tests.","security":[],"responses":{"200":{"description":"Service is reachable","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["ok"],"example":"ok"},"version":{"type":"string","example":"a1b2c3d"},"request_id":{"type":"string","example":"req_abcdef123"}},"required":["status","version","request_id"]}}}}}}},"/api/v1/whoami":{"get":{"tags":["meta"],"summary":"Identify the authenticated API key","description":"Returns the organisation id and scope the presented API key resolves to.\n\nUse during SDK setup to confirm the key works and is scoped correctly. Also handy for key rotation scripts: call /whoami before and after rotation to verify the new key resolves to the same organisation.","responses":{"200":{"description":"Authenticated context","content":{"application/json":{"schema":{"type":"object","properties":{"organisation_id":{"type":"string","example":"cm3org00001abc123xyz456"},"scope":{"type":"string","enum":["READ","WRITE"],"example":"READ"}},"required":["organisation_id","scope"]}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/recommendation-sets":{"get":{"tags":["recommendation-sets"],"summary":"List recommendation sets","description":"Paginated list of active (non-archived) recommendation sets, ordered by `created_at` descending.\n\n**Pagination**: `?limit=10&starting_after=<set_id>`. Default limit 10, max 100.\n\n`total_amount_cents` and `amount_cents` are integer minor units of the org's default currency.","responses":{"200":{"description":"List of recommendation sets","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RecommendationSet"}},"has_more":{"type":"boolean"}},"required":["data","has_more"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"tags":["recommendation-sets"],"summary":"Create a recommendation set","description":"Create a recommendation set in the authenticated organisation.\n\n`name` and at least one allocation are required. Each allocation references a `recipient_org_account_link_id` from this org, with an integer `amount_cents` in the org's default currency.\n\n`donor_prefill` is an optional flat string map (e.g. `{ email, firstName, lastName }`) used to pre-fill the public donation form.\n\nA foreign or unknown `recipient_org_account_link_id` returns 400 `invalid_request` and writes nothing.\n\nSend an `Idempotency-Key` header to make retries safe.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":500},"allocations":{"type":"array","items":{"type":"object","properties":{"recipient_org_account_link_id":{"type":"string","minLength":1},"amount_cents":{"type":"integer","exclusiveMinimum":0,"maximum":2147483647}},"required":["recipient_org_account_link_id","amount_cents"]},"minItems":1},"donor_prefill":{"type":"object","additionalProperties":{"type":"string"}},"description":{"type":"string","maxLength":2000},"expires_at":{"type":"string","format":"date-time"}},"required":["name","allocations"]}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecommendationSetDetail"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/recommendation-sets/{id}":{"get":{"tags":["recommendation-sets"],"summary":"Retrieve a recommendation set","description":"Fetch a single recommendation set by id, including its allocations. Returns 404 with the standard error envelope if no set with that id belongs to your organisation.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Recommendation set","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecommendationSetDetail"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"tags":["recommendation-sets"],"summary":"Deactivate a recommendation set","description":"Deactivate a recommendation set. The public donation URL stops accepting new donations; in-flight split payments complete normally.\n\nReturns 404 if the set does not belong to your organisation, or 400 if the set is already deactivated or archived.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Deactivated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecommendationSetDetail"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/donors":{"get":{"tags":["donors"],"summary":"List donors","description":"Paginated list of donors, ordered by `created_at` descending.\n\n**Pagination**: `?limit=10&starting_after=<donor_id>`. Default limit 10, max 100. The `has_more` boolean indicates whether another page exists.\n\n**Filters**:\n- `email`: exact match\n- `donor_type`: one of INDIVIDUAL, ORGANISATION, TRUST, UNKNOWN\n- `created_at[gte]`, `created_at[lte]`: ISO 8601 datetime bounds\n\nArchived (soft-deleted) donors are included in the list with `archived_at` set. Filter them out client-side if you only want active donors.","responses":{"200":{"description":"List of donors","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Donor"}},"has_more":{"type":"boolean"}},"required":["data","has_more"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"tags":["donors"],"summary":"Create a donor","description":"Create a new donor in the authenticated organisation.\n\nThe organisation is derived from the bearer key; do not pass an organisation id in the body.\n\nIf a non-archived donor with the same `email` already exists in this organisation, the request returns `409 conflict`. Use PATCH against the existing donor id, or force a distinct email, rather than retrying.\n\nSend an `Idempotency-Key` header to make retries safe. Replay of the same key + same body returns the original response; same key + different body returns `409 idempotency_conflict`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"donor_type":{"type":"string","enum":["INDIVIDUAL","ORGANISATION","TRUST","UNKNOWN"],"example":"INDIVIDUAL"},"first_name":{"type":"string","minLength":1,"maxLength":100,"example":"Alice"},"last_name":{"type":"string","minLength":1,"maxLength":100,"example":"Ng"},"organisation_name":{"type":"string","maxLength":200,"example":"Acme Pty Ltd"},"email":{"type":"string","format":"email","example":"alice@example.com"},"phone":{"type":"string","maxLength":20,"example":"0412345678"},"address_line1":{"type":"string","maxLength":200,"example":"42 Smith St"},"address_line2":{"type":"string","maxLength":200},"suburb":{"type":"string","maxLength":100,"example":"Fitzroy"},"state":{"type":"string","maxLength":20,"example":"VIC"},"postcode":{"type":"string","pattern":"^\\d{4}$","example":"3065"},"country":{"type":"string","maxLength":100,"example":"AU"},"employer_name":{"type":"string","maxLength":200},"occupation":{"type":"string","maxLength":200},"notes":{"type":"string","maxLength":5000}}}}}},"responses":{"201":{"description":"Donor created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Donor"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/donors/{id}":{"get":{"tags":["donors"],"summary":"Retrieve a donor","description":"Fetch a single donor by id. Returns 404 with the standard error envelope if no donor with that id belongs to your organisation.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Donor","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Donor"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"tags":["donors"],"summary":"Update a donor","description":"Partial update of a donor owned by the authenticated organisation. Only fields in the body are changed; omitted fields are left alone.\n\nReturns 404 if no donor with that id belongs to your organisation - this also guards against cross-tenant id guessing.\n\nSend an `Idempotency-Key` header to make retries safe.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"donor_type":{"type":"string","enum":["INDIVIDUAL","ORGANISATION","TRUST","UNKNOWN"],"example":"INDIVIDUAL"},"first_name":{"type":"string","minLength":1,"maxLength":100,"example":"Alice"},"last_name":{"type":"string","minLength":1,"maxLength":100,"example":"Ng"},"organisation_name":{"type":"string","maxLength":200,"example":"Acme Pty Ltd"},"email":{"type":"string","format":"email","example":"alice@example.com"},"phone":{"type":"string","maxLength":20,"example":"0412345678"},"address_line1":{"type":"string","maxLength":200,"example":"42 Smith St"},"address_line2":{"type":"string","maxLength":200},"suburb":{"type":"string","maxLength":100,"example":"Fitzroy"},"state":{"type":"string","maxLength":20,"example":"VIC"},"postcode":{"type":"string","pattern":"^\\d{4}$","example":"3065"},"country":{"type":"string","maxLength":100,"example":"AU"},"employer_name":{"type":"string","maxLength":200},"occupation":{"type":"string","maxLength":200},"notes":{"type":"string","maxLength":5000}}}}}},"responses":{"200":{"description":"Donor updated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Donor"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/donations":{"get":{"tags":["donations"],"summary":"List donations","description":"Paginated list of donations, ordered by `donation_date` descending (most recent first). Soft-deleted donations are excluded.\n\n**Pagination**: `?limit=10&starting_after=<donation_id>`. Default limit 10, max 100.\n\n**Filters**:\n- `donor_id`: exact match; scope to one donor's giving history\n- `source`: one of STRIPE, NATIONBUILDER, RAISELY, GATEWAY, CHECKOUT_LINK, SPLIT_GATEWAY, MANUAL, OTHER\n- `status`: one of PENDING, CONFIRMED, REFUNDED, FAILED. Defaults to CONFIRMED (settled donations only). Pass `?status=PENDING` to see donations the donor has submitted that are still in-flight (BECS / PayTo bank settlement, or pre-Stripe rows captured before the API call), or `?status=FAILED` for diagnostic visibility on failed attempts (Stripe errors, card declines, dishonours, transfer failures). For the full lifecycle in real time, subscribe to the `donation.created`, `donation.processing`, `donation.succeeded`, and `donation.failed` webhook events instead of polling.\n- `tracking_code`: exact match; attribute to a campaign\n- `donation_date[gte]`, `donation_date[lte]`: ISO 8601 bounds\n- `created_at[gte]`, `created_at[lte]`: ISO 8601 bounds\n\n`amount_cents` and `refunded_amount_cents` are integer minor units of the donation's `currency` (AUD cents, USD cents, etc.). `amount_cents` is always donor intent (gross); for `source: SPLIT_GATEWAY` it is the donor's gross intent toward this recipient, not the post-fee amount transferred.","responses":{"200":{"description":"List of donations","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Donation"}},"has_more":{"type":"boolean"}},"required":["data","has_more"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/donations/{id}":{"get":{"tags":["donations"],"summary":"Retrieve a donation","description":"Fetch a single donation by id. Returns 404 if no donation with that id belongs to your organisation, or if the donation is soft-deleted. Unlike the list endpoint, this returns donations of any status (PENDING / CONFIRMED / REFUNDED / FAILED) - the caller has the id, so pre-filtering would just hide a known-good lookup.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Donation","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Donation"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/forms":{"get":{"tags":["forms"],"summary":"List donation forms","description":"Paginated list of donation forms, ordered by `created_at` descending.\n\n**Pagination**: `?limit=10&starting_after=<form_id>`. Default limit 10, max 100.\n\n**Filters**:\n- `status`: one of DRAFT, ACTIVE, PAUSED, ARCHIVED\n- `created_at[gte]`, `created_at[lte]`: ISO 8601 datetime bounds\n\nArchived forms are included with `archived_at` set. Filter them out client-side if you only want live forms.","responses":{"200":{"description":"List of forms","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DonationForm"}},"has_more":{"type":"boolean"}},"required":["data","has_more"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"tags":["forms"],"summary":"Create a donation form","description":"Create a new donation form in the authenticated organisation.\n\n`name`, `slug`, and `title` are required; all other fields have sensible defaults.\n\nIf a form with the same `slug` already exists in this organisation, the request returns `409 conflict`. Use PATCH against the existing form id, or pick a distinct slug.\n\nSend an `Idempotency-Key` header to make retries safe.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"example":"General"},"slug":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","example":"general"},"status":{"type":"string","enum":["DRAFT","ACTIVE","PAUSED","ARCHIVED"],"example":"ACTIVE"},"title":{"type":"string","minLength":1,"maxLength":300,"example":"Support our campaign"},"description":{"type":"string","maxLength":5000},"thank_you_message":{"type":"string","maxLength":2000},"suggested_amounts_cents":{"type":"array","items":{"type":"integer","exclusiveMinimum":0,"maximum":9999999999}},"allow_custom_amount":{"type":"boolean"},"default_amount_cents":{"type":"integer","exclusiveMinimum":0,"maximum":9999999999},"minimum_amount_cents":{"type":"integer","exclusiveMinimum":0,"maximum":9999999999},"maximum_amount_cents":{"type":"integer","exclusiveMinimum":0,"maximum":9999999999},"allow_recurring":{"type":"boolean"},"recurring_frequencies":{"type":"array","items":{"type":"string","enum":["WEEKLY","FORTNIGHTLY","MONTHLY","QUARTERLY","ANNUALLY"]}},"address_visibility":{"type":"string","enum":["NONE","OPTIONAL","REQUIRED"]},"phone_visibility":{"type":"string","enum":["NONE","OPTIONAL","REQUIRED"]},"disclaimer_text":{"type":"string","maxLength":5000},"tracking_code":{"type":"string","maxLength":100},"embed_allowed_domains":{"type":"array","items":{"type":"string","minLength":1,"maxLength":253,"pattern":"^(\\*\\.)?([a-z0-9]([-a-z0-9]*[a-z0-9])?\\.)+[a-z]{2,}(:[0-9]{1,5})?$/i"},"maxItems":50}}}}}},"responses":{"201":{"description":"Form created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DonationForm"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/forms/{id}":{"get":{"tags":["forms"],"summary":"Retrieve a donation form","description":"Fetch a single donation form by id. Returns 404 with the standard error envelope if no form with that id belongs to your organisation.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Donation form","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DonationForm"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"tags":["forms"],"summary":"Update a donation form","description":"Partial update of a form owned by the authenticated organisation. Only fields in the body are changed; omitted fields are left alone.\n\nReturns 404 if no form with that id belongs to your organisation, or 409 if the updated slug collides with another form.\n\nSend an `Idempotency-Key` header to make retries safe.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":200,"example":"General"},"slug":{"type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9]+(?:-[a-z0-9]+)*$","example":"general"},"status":{"type":"string","enum":["DRAFT","ACTIVE","PAUSED","ARCHIVED"],"example":"ACTIVE"},"title":{"type":"string","minLength":1,"maxLength":300,"example":"Support our campaign"},"description":{"type":"string","maxLength":5000},"thank_you_message":{"type":"string","maxLength":2000},"suggested_amounts_cents":{"type":"array","items":{"type":"integer","exclusiveMinimum":0,"maximum":9999999999}},"allow_custom_amount":{"type":"boolean"},"default_amount_cents":{"type":"integer","exclusiveMinimum":0,"maximum":9999999999},"minimum_amount_cents":{"type":"integer","exclusiveMinimum":0,"maximum":9999999999},"maximum_amount_cents":{"type":"integer","exclusiveMinimum":0,"maximum":9999999999},"allow_recurring":{"type":"boolean"},"recurring_frequencies":{"type":"array","items":{"type":"string","enum":["WEEKLY","FORTNIGHTLY","MONTHLY","QUARTERLY","ANNUALLY"]}},"address_visibility":{"type":"string","enum":["NONE","OPTIONAL","REQUIRED"]},"phone_visibility":{"type":"string","enum":["NONE","OPTIONAL","REQUIRED"]},"disclaimer_text":{"type":"string","maxLength":5000},"tracking_code":{"type":"string","maxLength":100},"embed_allowed_domains":{"type":"array","items":{"type":"string","minLength":1,"maxLength":253,"pattern":"^(\\*\\.)?([a-z0-9]([-a-z0-9]*[a-z0-9])?\\.)+[a-z]{2,}(:[0-9]{1,5})?$/i"},"maxItems":50}}}}}},"responses":{"200":{"description":"Form updated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DonationForm"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"tags":["forms"],"summary":"Archive a donation form","description":"Archive a donation form. Archival sets `archived_at` and transitions `status` to `ARCHIVED`. The public donation URL for this form stops accepting new donations.\n\nReturns 404 if the form does not belong to your organisation or is already archived.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Form archived","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/DonationForm"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/checkout-links":{"get":{"tags":["checkout-links"],"summary":"List checkout links","description":"Paginated list of checkout links, ordered by `created_at` descending.\n\n**Pagination**: `?limit=10&starting_after=<link_id>`. Default limit 10, max 100.\n\n**Filters**:\n- `status`: one of ACTIVE, COMPLETED, EXPIRED, CANCELED\n- `created_at[gte]`, `created_at[lte]`: ISO 8601 datetime bounds\n\nArchived links are included with `archived_at` set.","responses":{"200":{"description":"List of checkout links","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CheckoutLink"}},"has_more":{"type":"boolean"}},"required":["data","has_more"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"tags":["checkout-links"],"summary":"Create a checkout link","description":"Create a one-off checkout link scoped to a single donor and amount. The link is addressable at `/c/{code}` on the public domain; visiting it redirects to a Stripe Checkout Session.\n\n`donor_email` and `amount_cents` are required; every other field is optional (donor name + compliance fields captured at link creation, not at donation time). `amount_cents` is integer minor units of the org's default currency.\n\nIf `stripe_integration_id` is provided, the link uses that Connect account; otherwise it falls back to the org's default integration.\n\nSend an `Idempotency-Key` header to make retries safe.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"donor_email":{"type":"string","format":"email","example":"alice@example.com"},"donor_first_name":{"type":"string","maxLength":100},"donor_last_name":{"type":"string","maxLength":100},"donor_phone":{"type":"string","maxLength":20},"amount_cents":{"type":"integer","exclusiveMinimum":0,"maximum":9999999999,"example":10000},"description":{"type":"string","maxLength":500},"address_line1":{"type":"string","maxLength":200},"address_line2":{"type":"string","maxLength":200},"suburb":{"type":"string","maxLength":100},"state":{"type":"string","maxLength":20},"postcode":{"type":"string","pattern":"^\\d{4}$"},"employer_name":{"type":"string","maxLength":200},"occupation":{"type":"string","maxLength":200},"tracking_code":{"type":"string","maxLength":100},"payment_methods":{"type":"array","items":{"type":"string","enum":["card","au_becs_debit","payto"]}},"stripe_integration_id":{"type":"string"}},"required":["donor_email","amount_cents"]}}}},"responses":{"201":{"description":"Checkout link created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CheckoutLink"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/checkout-links/{id}":{"get":{"tags":["checkout-links"],"summary":"Retrieve a checkout link","description":"Fetch a single checkout link by id. Returns 404 with the standard error envelope if no link with that id belongs to your organisation.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Checkout link","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CheckoutLink"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"tags":["checkout-links"],"summary":"Update a checkout link","description":"Partial update of an ACTIVE checkout link. Only `amount_cents`, `description`, and `payment_methods` are editable.\n\nReturns 404 if the link does not exist or belongs to another organisation, or 409 if the link is no longer ACTIVE (donor details + terminal-state links cannot be edited; archive and create a new one).","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount_cents":{"type":"integer","exclusiveMinimum":0,"maximum":9999999999},"description":{"type":"string","maxLength":500},"payment_methods":{"type":"array","items":{"type":"string","enum":["card","au_becs_debit","payto"]}}}}}}},"responses":{"200":{"description":"Checkout link updated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CheckoutLink"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"tags":["checkout-links"],"summary":"Archive a checkout link","description":"Archive a checkout link. If the link is still ACTIVE, it is atomically cancelled (status: CANCELED) so the public URL stops accepting donations. Emits both `checkout_link.cancelled` (when status changes) and `checkout_link.archived`.\n\nReturns 404 if the link does not exist, or 409 if it's already archived.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Checkout link archived","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/CheckoutLink"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/organisations/me":{"get":{"tags":["organisations"],"summary":"Retrieve the authenticated organisation","description":"Returns a snapshot of the organisation the API key belongs to. Useful for:\n\n- SDK setup: confirm the key resolves to the expected organisation\n- Sandbox detection: `is_sandbox` is true on the sandbox pair\n- Currency and timezone context for downstream display logic\n\nRequires a READ-scope key.","responses":{"200":{"description":"Organisation","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/OrganisationMe"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/organisations":{"get":{"tags":["organisations"],"summary":"List linked organisations","description":"Paginated directory of organisations the authenticated org has at least one active recipient-account link to. Use this to discover the recipient orgs you're authorised to direct donations to.\n\n**Pagination**: `?limit=10&starting_after=<org_id>`. Default limit 10, max 100. Ordered by org name ascending (alphabetical).\n\n**Privacy**: this endpoint never exposes orgs that the caller has no linkage to - there is no way to enumerate the platform's tenant list via this surface. The caller's own org is also excluded from the list (use `/organisations/me` for self).\n\nEach row returns the limited projection (no timezone, currency, compliance config, branding, or integration secrets).","responses":{"200":{"description":"List of linked organisations","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/OrganisationLinked"}},"has_more":{"type":"boolean"}},"required":["data","has_more"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/organisations/{id}":{"get":{"tags":["organisations"],"summary":"Retrieve a linked organisation","description":"Limited projection of one organisation the caller is linked to. Returns 404 if the caller has no active recipient-account link into the target org - the endpoint never reveals which organisations exist on the platform.\n\nFor your own organisation, use `/organisations/me` for the rich self-view; this endpoint returns the limited projection regardless of whether `{id}` is the caller's own.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Linked organisation","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/OrganisationLinked"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/recipient-accounts":{"get":{"tags":["recipient-accounts"],"summary":"List recipient accounts","description":"Paginated list of recipient-org account links the authenticated organisation holds. Each row encodes the distributor's authority to direct donor money to one Stripe Connect account at one recipient org.\n\n**Pagination**: `?limit=10&starting_after=<id>`. Default limit 10, max 100.\n\nUse the returned `id` as the `recipient_org_account_link_id` when building `/api/v1/recommendation-sets` allocations.\n\nInactive (deactivated) links are returned with `is_active=false`. Filter client-side if you only want active links.","responses":{"200":{"description":"List of recipient accounts","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RecipientAccount"}},"has_more":{"type":"boolean"}},"required":["data","has_more"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"post":{"tags":["recipient-accounts"],"summary":"Create a recipient account link","description":"Establish a link to a recipient organisation's Stripe Connect account by exchanging the recipient's distribution passphrase.\n\nThe recipient publishes a passphrase from their `/settings/engage/distribution` page; the distributing org sends it here together with a free-text `label` they'll use to refer to this account internally.\n\nReturns 400 if the passphrase doesn't resolve, the recipient account isn't fully onboarded, or an active link already exists. A previously-deactivated link is reactivated (and the new `label` replaces the old one) - that path also returns 201.\n\nSend an `Idempotency-Key` header to make retries safe.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"passphrase":{"type":"string","minLength":1,"maxLength":200},"label":{"type":"string","minLength":1,"maxLength":500}},"required":["passphrase","label"]}}}},"responses":{"201":{"description":"Recipient account created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecipientAccountDetail"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/recipient-accounts/{id}":{"get":{"tags":["recipient-accounts"],"summary":"Retrieve a recipient account","description":"Fetch one recipient-org account link by id, including the underlying Stripe Connect account id and `charges_enabled` flag. Returns 404 if no link with that id belongs to your organisation.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Recipient account","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecipientAccountDetail"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"patch":{"tags":["recipient-accounts"],"summary":"Update a recipient account label","description":"Update the distributor-side `label` on an existing link. Only the label is editable - the underlying account binding, recipient org, and Stripe integration are immutable for this row (deactivate and re-create with the recipient's passphrase to change the binding).\n\nReturns 404 if the link doesn't belong to your organisation.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","minLength":1,"maxLength":500}},"required":["label"]}}}},"responses":{"200":{"description":"Recipient account updated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecipientAccountDetail"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"tags":["recipient-accounts"],"summary":"Deactivate a recipient account link","description":"Deactivate the link. New `/recommendation-sets` allocations cannot reference a deactivated link; existing allocations and donations already attributed to it remain unchanged. To re-enable, POST again with the same passphrase + new label - that path reactivates and emits `recipient_org_account_link.created`.\n\nReturns 404 if the link doesn't belong to your organisation, 400 if it is already inactive.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Recipient account deactivated","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/RecipientAccountDetail"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/introductions":{"post":{"tags":["introductions"],"summary":"Create an introduction","description":"Send an introduction to a prospective recipient organisation. The platform mints a one-time invite link, optionally sends an email containing your `invitee_message` (if your org has email send infra wired), and tracks the invitee through the lifecycle: `pending → viewed → accepted → linked`. Listen for `introduction.linked` to learn when the resulting `recipient_org_account_link_id` is usable in a recommendation-set allocation.\n\n**Requires the Raise plan or higher.** Free-tier orgs can receive introductions (the public wizard) but cannot send them via the API.\n\n**Confidentiality**: `invitee_email` is the address you typed; it never updates and webhook payloads always echo it verbatim, even if the invitee signs in with a different address. Whether the invitee was a new or existing Together customer is never surfaced to you - declined introductions collapse to `expired` after the TTL.\n\n**Rate limiting**: cap of 50 PENDING introductions per organisation per rolling 24h. The 51st returns 400 with a `\"Daily introduction cap reached\"` message. (Soft anti-spam rail; future change to a structured 429 with `Retry-After` is on the roadmap.)\n\n`external_id` enables CRM stitching - it's echoed in every webhook event for this introduction. If the same `(organisationId, external_id)` already exists, the request returns 400.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"invitee_email":{"type":"string","format":"email","example":"alice@example.com"},"invitee_org_name_hint":{"type":"string","maxLength":200},"invitee_message":{"type":"string","maxLength":1000},"external_id":{"type":"string","maxLength":200},"ttl_days":{"type":"integer","minimum":1,"maximum":90}},"required":["invitee_email"]}}}},"responses":{"201":{"description":"Introduction created","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Introduction"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"409":{"description":"State conflict — either an `Idempotency-Key` is still in flight (`conflict`), the key was reused with a different body (`idempotency_conflict`), or a resource-specific uniqueness rule was violated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"get":{"tags":["introductions"],"summary":"List introductions","description":"Paginated list of introductions sent by the authenticated organisation, ordered by `created_at` descending.\n\n**Pagination**: `?limit=10&starting_after=<id>`. Default 10, max 100.\n\n**Filters**:\n- `status`: one of PENDING, VIEWED, ACCEPTED, LINKED, EXPIRED, REVOKED\n- `external_id`: exact match (use to look up a CRM-linked introduction)","parameters":[{"schema":{"type":"string"},"required":false,"name":"limit","in":"query"},{"schema":{"type":"string"},"required":false,"name":"starting_after","in":"query"},{"schema":{"type":"string","enum":["PENDING","VIEWED","ACCEPTED","LINKED","EXPIRED","REVOKED"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"string"},"required":false,"name":"external_id","in":"query"}],"responses":{"200":{"description":"List of introductions","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Introduction"}},"has_more":{"type":"boolean"}},"required":["data","has_more"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/introductions/{id}":{"get":{"tags":["introductions"],"summary":"Retrieve an introduction","description":"Fetch a single introduction by id. Returns 404 if the introduction does not belong to your organisation.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Introduction","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Introduction"}},"required":["data"]}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}},"delete":{"tags":["introductions"],"summary":"Revoke an introduction","description":"Cancel an in-flight introduction. Allowed only while the introduction is `pending`, `viewed`, or `accepted` - already-`linked` introductions are not revocable (the linkage is the agreement; deactivate the resulting `/recipient-accounts/{id}` instead).\n\nReturns 404 if the introduction doesn't belong to your organisation, 400 if it's in a terminal state.","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Introduction revoked","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Introduction"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}},"/api/v1/introductions/{id}/resend_email":{"post":{"tags":["introductions"],"summary":"Resend the invite email for an existing introduction","description":"Resend the invite email without minting a new token. Capped at 3 resends per introduction; a 4th call returns 400. Only valid while the introduction is `pending` or `viewed` - terminal states reject.\n\nUse this when the invitee says they didn't receive the original. To re-engage a prospect who never accepted, send a fresh introduction instead - this preserves your `external_id` audit trail for the original outreach.\n\n**Requires the Raise plan or higher** (same gate as POST).","parameters":[{"schema":{"type":"string"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Resend recorded","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Introduction"}},"required":["data"]}}}},"400":{"description":"Bad request — either malformed input (`invalid_request`) or input that failed validation (`validation_failed`, includes a `param` pointer).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"401":{"description":"Missing or invalid API key (`unauthorized`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"403":{"description":"Authenticated but not allowed — e.g. a READ key calling a WRITE endpoint, or an entitlement gate (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"404":{"description":"Resource does not exist or is not visible to the authenticated organisation (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}},"429":{"description":"Per-organisation rate limit exceeded (`rate_limited`). Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"}}}}}}}},"webhooks":{"donor.created":{"post":{"summary":"donor.created","description":"A donor record was created in this organisation.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donor.created"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_type":{"type":"string","enum":["INDIVIDUAL","ORGANISATION","TRUST","UNKNOWN"]},"email":{"type":["string","null"]},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"organisation_name":{"type":["string","null"]}},"required":["id","donor_type","email","first_name","last_name","organisation_name"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donor.updated":{"post":{"summary":"donor.updated","description":"A donor record was updated. Fires once per update; dedup is by donor id + updatedAt + a short content hash so concurrent emits for the same update collapse to one event even when updatedAt collides to the millisecond.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donor.updated"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_type":{"type":"string","enum":["INDIVIDUAL","ORGANISATION","TRUST","UNKNOWN"]},"email":{"type":["string","null"]},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"organisation_name":{"type":["string","null"]}},"required":["id","donor_type","email","first_name","last_name","organisation_name"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donor.archived":{"post":{"summary":"donor.archived","description":"A donor record was soft-archived (the `archivedAt` field was set). The payload carries the donor body at the moment of archival.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donor.archived"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_type":{"type":"string","enum":["INDIVIDUAL","ORGANISATION","TRUST","UNKNOWN"]},"email":{"type":["string","null"]},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"organisation_name":{"type":["string","null"]}},"required":["id","donor_type","email","first_name","last_name","organisation_name"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donor.merged":{"post":{"summary":"donor.merged","description":"Two donor records were merged. `data.survivor` is the donor that remains; `data.merged_donor_id` is the one that was archived into it. Donations, external ids, and disclosures attributed to the merged donor have been reassigned.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donor.merged"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"survivor":{"type":"object","properties":{"id":{"type":"string"},"donor_type":{"type":"string","enum":["INDIVIDUAL","ORGANISATION","TRUST","UNKNOWN"]},"email":{"type":["string","null"]},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"organisation_name":{"type":["string","null"]}},"required":["id","donor_type","email","first_name","last_name","organisation_name"]},"merged_donor_id":{"type":"string"},"donations_reassigned":{"type":"integer","minimum":0}},"required":["survivor","merged_donor_id","donations_reassigned"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donation.created":{"post":{"summary":"donation.created","description":"A Donation row was written in this organisation - the Together-side record exists, but funds may not yet have settled. For the Stripe forms / checkout-link / split paths the row is pre-created BEFORE the Stripe API call so 'donor pressed submit and our DB has it' is observable even if Stripe is unreachable; in that case the very next event for the same id is `donation.failed`. For paths with no in-flight phase (subscription renewals, NB / Raisely sync, manual entry) `donation.created` is followed immediately by `donation.succeeded` for a consistent shape. The payload's `status` is PENDING. Currency amounts are integer minor units of `currency` (cents for AUD/USD).","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donation.created"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_id":{"type":"string"},"amount_cents":{"type":"integer"},"refunded_amount_cents":{"type":"integer"},"currency":{"type":"string"},"donation_date":{"type":"string"},"source":{"type":"string","enum":["NATIONBUILDER","STRIPE","RAISELY","GATEWAY","CHECKOUT_LINK","SPLIT_GATEWAY","MANUAL","OTHER"]},"status":{"type":"string","enum":["PENDING","CONFIRMED","REFUNDED","FAILED"]},"external_id":{"type":["string","null"]},"payment_method":{"type":["string","null"]},"tracking_code":{"type":["string","null"]},"deleted_at":{"type":["string","null"]},"failure_code":{"type":["string","null"]},"failure_message":{"type":["string","null"]}},"required":["id","donor_id","amount_cents","refunded_amount_cents","currency","donation_date","source","status","external_id","payment_method","tracking_code","deleted_at","failure_code","failure_message"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donation.processing":{"post":{"summary":"donation.processing","description":"An async-clearing payment (BECS Direct Debit / PayTo) entered the bank network. Fires from `payment_intent.processing` (forms + initial subscription) and from `checkout.session.completed` with `payment_status=processing` (checkout-link). Card payments do NOT emit this event - they go straight from `donation.created` to `donation.succeeded`. The payload's `status` is PENDING. Terminal once per donation id; subsequent retries collapse via dedup.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donation.processing"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_id":{"type":"string"},"amount_cents":{"type":"integer"},"refunded_amount_cents":{"type":"integer"},"currency":{"type":"string"},"donation_date":{"type":"string"},"source":{"type":"string","enum":["NATIONBUILDER","STRIPE","RAISELY","GATEWAY","CHECKOUT_LINK","SPLIT_GATEWAY","MANUAL","OTHER"]},"status":{"type":"string","enum":["PENDING","CONFIRMED","REFUNDED","FAILED"]},"external_id":{"type":["string","null"]},"payment_method":{"type":["string","null"]},"tracking_code":{"type":["string","null"]},"deleted_at":{"type":["string","null"]},"failure_code":{"type":["string","null"]},"failure_message":{"type":["string","null"]}},"required":["id","donor_id","amount_cents","refunded_amount_cents","currency","donation_date","source","status","external_id","payment_method","tracking_code","deleted_at","failure_code","failure_message"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donation.succeeded":{"post":{"summary":"donation.succeeded","description":"Funds settled for this donation. Fires from `payment_intent.succeeded` (forms), `checkout.session.async_payment_succeeded` and `checkout.session.completed` with `payment_status=paid` (checkout-link), `transfer.created` (split / recommendation-set children), `invoice.payment_succeeded` (subscription renewals), and from the `reconcile-pending-donations` cron when it resolves a stale PENDING row. Together-side compliance recalculation, disclosure detection, and CRM write-back enqueue happen in the same transaction as the row flip; this event is the canonical 'donation is real money' signal for downstream integrators. The payload's `status` is CONFIRMED. Terminal once per donation id; webhook re-deliveries collapse via dedup.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donation.succeeded"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_id":{"type":"string"},"amount_cents":{"type":"integer"},"refunded_amount_cents":{"type":"integer"},"currency":{"type":"string"},"donation_date":{"type":"string"},"source":{"type":"string","enum":["NATIONBUILDER","STRIPE","RAISELY","GATEWAY","CHECKOUT_LINK","SPLIT_GATEWAY","MANUAL","OTHER"]},"status":{"type":"string","enum":["PENDING","CONFIRMED","REFUNDED","FAILED"]},"external_id":{"type":["string","null"]},"payment_method":{"type":["string","null"]},"tracking_code":{"type":["string","null"]},"deleted_at":{"type":["string","null"]},"failure_code":{"type":["string","null"]},"failure_message":{"type":["string","null"]}},"required":["id","donor_id","amount_cents","refunded_amount_cents","currency","donation_date","source","status","external_id","payment_method","tracking_code","deleted_at","failure_code","failure_message"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donation.refunded":{"post":{"summary":"donation.refunded","description":"A donation was refunded (full or partial). Fires from charge.refunded and from charge.dispute.funds_withdrawn (BECS dispute auto-debit). The payload's `refunded_amount_cents` is the new total refunded; `deleted_at` is set when the donation is fully refunded (i.e. refunded_amount_cents >= amount_cents). Dedup is per refund event so a partial then a full refund produce two events.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donation.refunded"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_id":{"type":"string"},"amount_cents":{"type":"integer"},"refunded_amount_cents":{"type":"integer"},"currency":{"type":"string"},"donation_date":{"type":"string"},"source":{"type":"string","enum":["NATIONBUILDER","STRIPE","RAISELY","GATEWAY","CHECKOUT_LINK","SPLIT_GATEWAY","MANUAL","OTHER"]},"status":{"type":"string","enum":["PENDING","CONFIRMED","REFUNDED","FAILED"]},"external_id":{"type":["string","null"]},"payment_method":{"type":["string","null"]},"tracking_code":{"type":["string","null"]},"deleted_at":{"type":["string","null"]},"failure_code":{"type":["string","null"]},"failure_message":{"type":["string","null"]}},"required":["id","donor_id","amount_cents","refunded_amount_cents","currency","donation_date","source","status","external_id","payment_method","tracking_code","deleted_at","failure_code","failure_message"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donation.refund_reversed":{"post":{"summary":"donation.refund_reversed","description":"A previous refund was reversed - funds were credited back to the organisation. Fires from charge.dispute.funds_reinstated when a dispute is won after funds were already withdrawn. `refunded_amount_cents` is decremented; if the donation was previously soft-deleted by a full refund, `deleted_at` becomes null again.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donation.refund_reversed"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_id":{"type":"string"},"amount_cents":{"type":"integer"},"refunded_amount_cents":{"type":"integer"},"currency":{"type":"string"},"donation_date":{"type":"string"},"source":{"type":"string","enum":["NATIONBUILDER","STRIPE","RAISELY","GATEWAY","CHECKOUT_LINK","SPLIT_GATEWAY","MANUAL","OTHER"]},"status":{"type":"string","enum":["PENDING","CONFIRMED","REFUNDED","FAILED"]},"external_id":{"type":["string","null"]},"payment_method":{"type":["string","null"]},"tracking_code":{"type":["string","null"]},"deleted_at":{"type":["string","null"]},"failure_code":{"type":["string","null"]},"failure_message":{"type":["string","null"]}},"required":["id","donor_id","amount_cents","refunded_amount_cents","currency","donation_date","source","status","external_id","payment_method","tracking_code","deleted_at","failure_code","failure_message"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donation.failed":{"post":{"summary":"donation.failed","description":"A donation attempt failed terminally. Fires from: (a) Stripe API errors at submit time (after `donation.created` but before any clearing) - the row was written but Stripe rejected or was unreachable, so the Donation is flipped to FAILED and this event closes the lifecycle; (b) `payment_intent.payment_failed` for card declines at submit time and BECS / PayTo dishonours at clearing (which can land days after the donor submitted); (c) `checkout.session.async_payment_failed` for checkout-link BECS / PayTo dishonours; (d) `invoice.payment_failed` for subscription renewal failures (`external_id` is the invoice id); (e) split-payment / recommendation-set transfer failures. `failure_code` mirrors Stripe's machine-readable code (e.g. `insufficient_funds`, `account_closed`); `failure_message` is the human-readable bank message (or our wrapper for non-Stripe-network errors). Receivers can always fetch the row via `GET /api/v1/donations/{id}`.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donation.failed"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_id":{"type":"string"},"amount_cents":{"type":"integer"},"refunded_amount_cents":{"type":"integer"},"currency":{"type":"string"},"donation_date":{"type":"string"},"source":{"type":"string","enum":["NATIONBUILDER","STRIPE","RAISELY","GATEWAY","CHECKOUT_LINK","SPLIT_GATEWAY","MANUAL","OTHER"]},"status":{"type":"string","enum":["PENDING","CONFIRMED","REFUNDED","FAILED"]},"external_id":{"type":["string","null"]},"payment_method":{"type":["string","null"]},"tracking_code":{"type":["string","null"]},"deleted_at":{"type":["string","null"]},"failure_code":{"type":["string","null"]},"failure_message":{"type":["string","null"]}},"required":["id","donor_id","amount_cents","refunded_amount_cents","currency","donation_date","source","status","external_id","payment_method","tracking_code","deleted_at","failure_code","failure_message"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donation_form.created":{"post":{"summary":"donation_form.created","description":"A donation form was created (embeddable Stripe-backed donation page). Fires once per id from both POST /api/v1/forms and the UI create action.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donation_form.created"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string","enum":["DRAFT","ACTIVE","PAUSED","ARCHIVED"]},"title":{"type":"string"},"description":{"type":["string","null"]},"archived_at":{"type":["string","null"]}},"required":["id","name","slug","status","title","description","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donation_form.updated":{"post":{"summary":"donation_form.updated","description":"A donation form's name, slug, status, or any field was edited (including status transitions DRAFT -> ACTIVE -> PAUSED). Dispatch on the `status` field in the payload to reconcile state.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donation_form.updated"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string","enum":["DRAFT","ACTIVE","PAUSED","ARCHIVED"]},"title":{"type":"string"},"description":{"type":["string","null"]},"archived_at":{"type":["string","null"]}},"required":["id","name","slug","status","title","description","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"donation_form.archived":{"post":{"summary":"donation_form.archived","description":"A donation form was archived. Archive is terminal: the public URL stops accepting new donations, status becomes ARCHIVED, archived_at is set.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["donation_form.archived"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"status":{"type":"string","enum":["DRAFT","ACTIVE","PAUSED","ARCHIVED"]},"title":{"type":"string"},"description":{"type":["string","null"]},"archived_at":{"type":["string","null"]}},"required":["id","name","slug","status","title","description","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"checkout_link.created":{"post":{"summary":"checkout_link.created","description":"A one-off checkout link was created (addressable at /c/{code}). Fires once per id from both POST /api/v1/checkout-links and the UI create action.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["checkout_link.created"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"code":{"type":"string"},"donor_email":{"type":"string"},"amount_cents":{"type":"integer"},"currency":{"type":"string"},"description":{"type":["string","null"]},"status":{"type":"string","enum":["ACTIVE","COMPLETED","EXPIRED","CANCELED"]},"archived_at":{"type":["string","null"]}},"required":["id","code","donor_email","amount_cents","currency","description","status","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"checkout_link.updated":{"post":{"summary":"checkout_link.updated","description":"A checkout link's amount_cents, description, or payment_methods was edited. Only ACTIVE links can be edited; any attempted edit of a COMPLETED / EXPIRED / CANCELED / archived link returns 409.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["checkout_link.updated"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"code":{"type":"string"},"donor_email":{"type":"string"},"amount_cents":{"type":"integer"},"currency":{"type":"string"},"description":{"type":["string","null"]},"status":{"type":"string","enum":["ACTIVE","COMPLETED","EXPIRED","CANCELED"]},"archived_at":{"type":["string","null"]}},"required":["id","code","donor_email","amount_cents","currency","description","status","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"checkout_link.cancelled":{"post":{"summary":"checkout_link.cancelled","description":"A checkout link was cancelled - the public URL stops accepting donations. Fires from explicit cancel OR as a side-effect of archiving an ACTIVE link. One-way transition.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["checkout_link.cancelled"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"code":{"type":"string"},"donor_email":{"type":"string"},"amount_cents":{"type":"integer"},"currency":{"type":"string"},"description":{"type":["string","null"]},"status":{"type":"string","enum":["ACTIVE","COMPLETED","EXPIRED","CANCELED"]},"archived_at":{"type":["string","null"]}},"required":["id","code","donor_email","amount_cents","currency","description","status","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"checkout_link.archived":{"post":{"summary":"checkout_link.archived","description":"A checkout link was archived (hidden from active lists). If the link was still ACTIVE when archived, archive also cancels it and emits checkout_link.cancelled before this event.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["checkout_link.archived"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"code":{"type":"string"},"donor_email":{"type":"string"},"amount_cents":{"type":"integer"},"currency":{"type":"string"},"description":{"type":["string","null"]},"status":{"type":"string","enum":["ACTIVE","COMPLETED","EXPIRED","CANCELED"]},"archived_at":{"type":["string","null"]}},"required":["id","code","donor_email","amount_cents","currency","description","status","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"disclosure.created":{"post":{"summary":"disclosure.created","description":"A disclosure obligation was created - a donor crossed the disclosure threshold in this organisation's current disclosure period. Payload carries the receipt number, trigger_amount_cents, deadline, and initial status (PENDING / DUE_SOON / OVERDUE depending on deadline proximity at creation).","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["disclosure.created"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_id":{"type":"string"},"disclosure_period_id":{"type":"string"},"receipt_number":{"type":"string"},"status":{"type":"string","enum":["PENDING","DUE_SOON","OVERDUE","PENDING_REVIEW","CANCELLED","ORG_DISCLOSED","FULLY_DISCLOSED"]},"jurisdiction":{"type":"string"},"trigger_amount_cents":{"type":"integer"},"trigger_date":{"type":"string"},"trigger_donation_id":{"type":["string","null"]},"period_type":{"type":"string","enum":["NORMAL","ELECTION","EXPEDITED"]},"disclosure_deadline":{"type":"string"},"org_disclosed_at":{"type":["string","null"]},"org_disclosure_ref":{"type":["string","null"]},"donor_notified_at":{"type":["string","null"]},"donor_confirmed_at":{"type":["string","null"]}},"required":["id","donor_id","disclosure_period_id","receipt_number","status","jurisdiction","trigger_amount_cents","trigger_date","trigger_donation_id","period_type","disclosure_deadline","org_disclosed_at","org_disclosure_ref","donor_notified_at","donor_confirmed_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"disclosure.updated":{"post":{"summary":"disclosure.updated","description":"A disclosure obligation changed stage. Fires on cron-driven status transitions (PENDING -> DUE_SOON -> OVERDUE) and user-driven transitions (mark org-disclosed, mark donor-confirmed, cancel). Dispatch on `data.status` to reconcile; `org_disclosed_at` / `donor_notified_at` / `donor_confirmed_at` carry audit timestamps.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["disclosure.updated"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"donor_id":{"type":"string"},"disclosure_period_id":{"type":"string"},"receipt_number":{"type":"string"},"status":{"type":"string","enum":["PENDING","DUE_SOON","OVERDUE","PENDING_REVIEW","CANCELLED","ORG_DISCLOSED","FULLY_DISCLOSED"]},"jurisdiction":{"type":"string"},"trigger_amount_cents":{"type":"integer"},"trigger_date":{"type":"string"},"trigger_donation_id":{"type":["string","null"]},"period_type":{"type":"string","enum":["NORMAL","ELECTION","EXPEDITED"]},"disclosure_deadline":{"type":"string"},"org_disclosed_at":{"type":["string","null"]},"org_disclosure_ref":{"type":["string","null"]},"donor_notified_at":{"type":["string","null"]},"donor_confirmed_at":{"type":["string","null"]}},"required":["id","donor_id","disclosure_period_id","receipt_number","status","jurisdiction","trigger_amount_cents","trigger_date","trigger_donation_id","period_type","disclosure_deadline","org_disclosed_at","org_disclosure_ref","donor_notified_at","donor_confirmed_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recommendation_set.created":{"post":{"summary":"recommendation_set.created","description":"A recommendation set was created. Fires once per id from both POST /api/v1/recommendation-sets and the UI create action.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recommendation_set.created"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url_key":{"type":"string"},"description":{"type":["string","null"]},"is_active":{"type":"boolean"},"archived_at":{"type":["string","null"]}},"required":["id","name","slug","url_key","description","is_active","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recommendation_set.updated":{"post":{"summary":"recommendation_set.updated","description":"A recommendation set's name, description, or donor prefill was edited.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recommendation_set.updated"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url_key":{"type":"string"},"description":{"type":["string","null"]},"is_active":{"type":"boolean"},"archived_at":{"type":["string","null"]}},"required":["id","name","slug","url_key","description","is_active","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recommendation_set.deactivated":{"post":{"summary":"recommendation_set.deactivated","description":"A recommendation set was deactivated - the public URL stops accepting new donations. Existing in-flight split payments complete normally.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recommendation_set.deactivated"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url_key":{"type":"string"},"description":{"type":["string","null"]},"is_active":{"type":"boolean"},"archived_at":{"type":["string","null"]}},"required":["id","name","slug","url_key","description","is_active","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recommendation_set.archived":{"post":{"summary":"recommendation_set.archived","description":"A recommendation set was archived. Archive is terminal: it implies deactivated + hidden from active lists.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recommendation_set.archived"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"slug":{"type":"string"},"url_key":{"type":"string"},"description":{"type":["string","null"]},"is_active":{"type":"boolean"},"archived_at":{"type":["string","null"]}},"required":["id","name","slug","url_key","description","is_active","archived_at"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recommendation_set.payment_succeeded":{"post":{"summary":"recommendation_set.payment_succeeded","description":"A donor paid through this recommendation set and the platform's PaymentIntent cleared. Fires once per `split_payment_id`. The platform now has the money on its Stripe balance and will run per-recipient transfers next - watch for `recommendation_set.distribution_succeeded` / `distribution_failed` events keyed on the same `split_payment_id` for the per-recipient outcomes. The donor receipt is sent at this point; subscribers do NOT need to send their own. See docs/platform/RECSETS-LIFECYCLE.md.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recommendation_set.payment_succeeded"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"split_payment_id":{"type":"string"},"recommendation_set_id":{"type":["string","null"]},"donor_id":{"type":["string","null"]},"total_amount_cents":{"type":"integer"},"currency":{"type":"string"},"payment_method_type":{"type":"string"},"donation_date":{"type":"string"},"failure_code":{"type":["string","null"]},"failure_message":{"type":["string","null"]}},"required":["split_payment_id","recommendation_set_id","donor_id","total_amount_cents","currency","payment_method_type","donation_date","failure_code","failure_message"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recommendation_set.payment_failed":{"post":{"summary":"recommendation_set.payment_failed","description":"A donor's PaymentIntent failed on a recommendation-set payment. Fires once per `split_payment_id`. No transfers are attempted, so no `distribution_*` events follow. `failure_code` mirrors Stripe's machine-readable code (e.g. `insufficient_funds`, `card_declined`, `account_closed` for au_becs_debit dishonours); `failure_message` is the bank-supplied reason. The donor was NOT charged.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recommendation_set.payment_failed"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"split_payment_id":{"type":"string"},"recommendation_set_id":{"type":["string","null"]},"donor_id":{"type":["string","null"]},"total_amount_cents":{"type":"integer"},"currency":{"type":"string"},"payment_method_type":{"type":"string"},"donation_date":{"type":"string"},"failure_code":{"type":["string","null"]},"failure_message":{"type":["string","null"]}},"required":["split_payment_id","recommendation_set_id","donor_id","total_amount_cents","currency","payment_method_type","donation_date","failure_code","failure_message"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recommendation_set.distribution_succeeded":{"post":{"summary":"recommendation_set.distribution_succeeded","description":"A per-recipient Stripe Transfer landed in the recipient's connected account. Fires once per `split_allocation_id`; an N-recipient recsets payment produces N of these events (mixed with `distribution_failed` if some allocations didn't land). The recipient also receives `donation.created` + `donation.succeeded` on their own org with the per-recipient `Donation` row id - this event is the recommending org's view of the same transfer, with `recipient_organisation_id` identifying which recipient got the money. `stripe_transfer_id` lets ops cross-reference the Stripe dashboard.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recommendation_set.distribution_succeeded"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"split_allocation_id":{"type":"string"},"split_payment_id":{"type":"string"},"recommendation_set_id":{"type":["string","null"]},"donor_id":{"type":["string","null"]},"recipient_organisation_id":{"type":"string"},"recipient_organisation_name":{"type":"string"},"amount_cents":{"type":"integer"},"currency":{"type":"string"},"stripe_transfer_id":{"type":["string","null"]},"failure_code":{"type":["string","null"]},"failure_message":{"type":["string","null"]}},"required":["split_allocation_id","split_payment_id","recommendation_set_id","donor_id","recipient_organisation_id","recipient_organisation_name","amount_cents","currency","stripe_transfer_id","failure_code","failure_message"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recommendation_set.distribution_failed":{"post":{"summary":"recommendation_set.distribution_failed","description":"A per-recipient Stripe Transfer failed. Money for this allocation is sitting on the platform's Stripe balance - the donor was charged (see the corresponding `recommendation_set.payment_succeeded`) but the recipient's connected account couldn't receive it (typically `account_invalid`, `recipient_account_not_active`, or `No such destination`). Fires once per `split_allocation_id`. **No `donation.failed` event is emitted on the recipient org** - the donor's gift didn't fail (we have the money), the distribution did. Ops resolution is either retry the transfer (after fixing the recipient's account) or refund the donor; neither is automated as of this event.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recommendation_set.distribution_failed"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"split_allocation_id":{"type":"string"},"split_payment_id":{"type":"string"},"recommendation_set_id":{"type":["string","null"]},"donor_id":{"type":["string","null"]},"recipient_organisation_id":{"type":"string"},"recipient_organisation_name":{"type":"string"},"amount_cents":{"type":"integer"},"currency":{"type":"string"},"stripe_transfer_id":{"type":["string","null"]},"failure_code":{"type":["string","null"]},"failure_message":{"type":["string","null"]}},"required":["split_allocation_id","split_payment_id","recommendation_set_id","donor_id","recipient_organisation_id","recipient_organisation_name","amount_cents","currency","stripe_transfer_id","failure_code","failure_message"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recipient_org_account_link.created":{"post":{"summary":"recipient_org_account_link.created","description":"A new recipient-org account link was established (the distributing org entered the recipient's distribution passphrase, or reactivated a previously deactivated link). The link encodes the distributing org's authority to direct donor money to the recipient's Stripe Connect account; donations are not implied by this event.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recipient_org_account_link.created"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"recipient_integration_id":{"type":"string"},"label":{"type":"string"},"is_active":{"type":"boolean"},"introduction_id":{"type":["string","null"]}},"required":["id","recipient_integration_id","label","is_active","introduction_id"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recipient_org_account_link.updated":{"post":{"summary":"recipient_org_account_link.updated","description":"The label on a recipient-org account link was edited. Fires once per label change; emits the new state. Only the distributing org can edit their own labels.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recipient_org_account_link.updated"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"recipient_integration_id":{"type":"string"},"label":{"type":"string"},"is_active":{"type":"boolean"},"introduction_id":{"type":["string","null"]}},"required":["id","recipient_integration_id","label","is_active","introduction_id"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"recipient_org_account_link.deactivated":{"post":{"summary":"recipient_org_account_link.deactivated","description":"A recipient-org account link was deactivated (`is_active=false`). The distributing org can no longer reference this link in new recommendation-set allocations; existing allocations are unaffected. Reactivation re-emits `recipient_org_account_link.created` (single state-change event per direction).","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["recipient_org_account_link.deactivated"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"recipient_integration_id":{"type":"string"},"label":{"type":"string"},"is_active":{"type":"boolean"},"introduction_id":{"type":["string","null"]}},"required":["id","recipient_integration_id","label","is_active","introduction_id"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"introduction.created":{"post":{"summary":"introduction.created","description":"An introduction was sent. The inviter calls `POST /api/v1/introductions` with `invitee_email` + optional `invitee_message` and `external_id`; the platform mints a token and sends the email. Use this to log the outbound trace in your CRM. Sandbox-org introductions emit this event but no email is sent.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["introduction.created"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","VIEWED","ACCEPTED","LINKED","EXPIRED","REVOKED"]},"invitee_email":{"type":"string"},"invitee_org_name_hint":{"type":["string","null"]},"external_id":{"type":["string","null"]},"expires_at":{"type":"string"},"viewed_at":{"type":["string","null"]},"accepted_at":{"type":["string","null"]},"linked_at":{"type":["string","null"]},"revoked_at":{"type":["string","null"]},"accepted_organisation_id":{"type":["string","null"]},"accepted_recipient_org_account_link_id":{"type":["string","null"]}},"required":["id","status","invitee_email","invitee_org_name_hint","external_id","expires_at","viewed_at","accepted_at","linked_at","revoked_at","accepted_organisation_id","accepted_recipient_org_account_link_id"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"introduction.viewed":{"post":{"summary":"introduction.viewed","description":"The invitee opened the invite link for the first time. Idempotent - re-opens do not re-emit. Useful as a soft funnel signal between `created` and `accepted`. The invitee has not signed in or committed to anything yet.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["introduction.viewed"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","VIEWED","ACCEPTED","LINKED","EXPIRED","REVOKED"]},"invitee_email":{"type":"string"},"invitee_org_name_hint":{"type":["string","null"]},"external_id":{"type":["string","null"]},"expires_at":{"type":"string"},"viewed_at":{"type":["string","null"]},"accepted_at":{"type":["string","null"]},"linked_at":{"type":["string","null"]},"revoked_at":{"type":["string","null"]},"accepted_organisation_id":{"type":["string","null"]},"accepted_recipient_org_account_link_id":{"type":["string","null"]}},"required":["id","status","invitee_email","invitee_org_name_hint","external_id","expires_at","viewed_at","accepted_at","linked_at","revoked_at","accepted_organisation_id","accepted_recipient_org_account_link_id"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"introduction.accepted":{"post":{"summary":"introduction.accepted","description":"The invitee signed in (or signed up) and explicitly clicked through the wizard's privacy callout, binding the introduction to a specific Together organisation (`accepted_organisation_id`). Stripe may or may not be ready - this event signals intent, not a working linkage. Listen for `introduction.linked` to know the inviter can actually reference the link in a recommendation set.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["introduction.accepted"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","VIEWED","ACCEPTED","LINKED","EXPIRED","REVOKED"]},"invitee_email":{"type":"string"},"invitee_org_name_hint":{"type":["string","null"]},"external_id":{"type":["string","null"]},"expires_at":{"type":"string"},"viewed_at":{"type":["string","null"]},"accepted_at":{"type":["string","null"]},"linked_at":{"type":["string","null"]},"revoked_at":{"type":["string","null"]},"accepted_organisation_id":{"type":["string","null"]},"accepted_recipient_org_account_link_id":{"type":["string","null"]}},"required":["id","status","invitee_email","invitee_org_name_hint","external_id","expires_at","viewed_at","accepted_at","linked_at","revoked_at","accepted_organisation_id","accepted_recipient_org_account_link_id"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"introduction.linked":{"post":{"summary":"introduction.linked","description":"The invitee's Stripe Connect account is set up and they confirmed sharing it with the inviter. `accepted_recipient_org_account_link_id` is the new link's id - use it as `recipient_org_account_link_id` when building recommendation-set allocations. Fires alongside `recipient_org_account_link.created`, which carries the link body and the same `introduction_id` for stitching.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["introduction.linked"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","VIEWED","ACCEPTED","LINKED","EXPIRED","REVOKED"]},"invitee_email":{"type":"string"},"invitee_org_name_hint":{"type":["string","null"]},"external_id":{"type":["string","null"]},"expires_at":{"type":"string"},"viewed_at":{"type":["string","null"]},"accepted_at":{"type":["string","null"]},"linked_at":{"type":["string","null"]},"revoked_at":{"type":["string","null"]},"accepted_organisation_id":{"type":["string","null"]},"accepted_recipient_org_account_link_id":{"type":["string","null"]}},"required":["id","status","invitee_email","invitee_org_name_hint","external_id","expires_at","viewed_at","accepted_at","linked_at","revoked_at","accepted_organisation_id","accepted_recipient_org_account_link_id"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"introduction.expired":{"post":{"summary":"introduction.expired","description":"The introduction's TTL elapsed without reaching `LINKED`. Terminal. Fires from an hourly cron sweep that scans `PENDING`/`VIEWED`/`ACCEPTED` rows past `expires_at`. To re-engage the same prospect, send a new introduction.","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["introduction.expired"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","VIEWED","ACCEPTED","LINKED","EXPIRED","REVOKED"]},"invitee_email":{"type":"string"},"invitee_org_name_hint":{"type":["string","null"]},"external_id":{"type":["string","null"]},"expires_at":{"type":"string"},"viewed_at":{"type":["string","null"]},"accepted_at":{"type":["string","null"]},"linked_at":{"type":["string","null"]},"revoked_at":{"type":["string","null"]},"accepted_organisation_id":{"type":["string","null"]},"accepted_recipient_org_account_link_id":{"type":["string","null"]}},"required":["id","status","invitee_email","invitee_org_name_hint","external_id","expires_at","viewed_at","accepted_at","linked_at","revoked_at","accepted_organisation_id","accepted_recipient_org_account_link_id"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}},"introduction.revoked":{"post":{"summary":"introduction.revoked","description":"The inviter cancelled the introduction via `DELETE /api/v1/introductions/{id}`. Terminal. Allowed only while the introduction is `PENDING`/`VIEWED`/`ACCEPTED` - already-`LINKED` introductions are not revocable (the linkage is the agreement; deactivate the link instead).","tags":["webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"type":{"type":"string","enum":["introduction.revoked"]},"occurred_at":{"type":"string"},"organisation_id":{"type":"string"},"data":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["PENDING","VIEWED","ACCEPTED","LINKED","EXPIRED","REVOKED"]},"invitee_email":{"type":"string"},"invitee_org_name_hint":{"type":["string","null"]},"external_id":{"type":["string","null"]},"expires_at":{"type":"string"},"viewed_at":{"type":["string","null"]},"accepted_at":{"type":["string","null"]},"linked_at":{"type":["string","null"]},"revoked_at":{"type":["string","null"]},"accepted_organisation_id":{"type":["string","null"]},"accepted_recipient_org_account_link_id":{"type":["string","null"]}},"required":["id","status","invitee_email","invitee_org_name_hint","external_id","expires_at","viewed_at","accepted_at","linked_at","revoked_at","accepted_organisation_id","accepted_recipient_org_account_link_id"]}},"required":["type","occurred_at","organisation_id","data"]}}}},"responses":{"200":{"description":"Receiver accepted the event. Return 2xx within 10 seconds; body is ignored."},"4xx":{"description":"Signature verification failed or the receiver rejected the payload. Counts as a failure; the delivery retries with exponential backoff."},"5xx":{"description":"Receiver errored. Counts as a failure; the delivery retries with exponential backoff up to 6 attempts over ~28h."}}}}}}