delete_delete_intro
ChatGPTReturns 404 when the intro doesn't exist or was found in an email you can't access.
Rings AI lets authenticated users search relationship data, inspect people and companies, manage private lists, create notes and tasks, and update CRM records from ChatGPT using the same tenant permissions as Rings.
deleteNote: only the note's owner or a ringmaster may delete it. Returns 204 on success, 404 when the note isn't visible to the caller's tenant, and 403 when the caller is neither the owner nor a ringmaster.removeAssociation with the association's target type + uuid (derived from the row). Returns 204 on success and 404 when the association doesn't exist or doesn't belong to the referenced note.deleted_count reports how many opportunities were actually removed, including cascaded sub-opportunities. Always returns 200, even when deleted_count is 0. Returns 400 if more than 100 UUIDs are submitted in one request (the cap applies to the requested UUIDs, not the cascade).removeEntityFromOpportunity. Returns 204 on success, 404 if the association doesn't exist or doesn't belong to the referenced opportunity.current_company reads as null afterward. Returns 204 on success (including when the person had no current company to begin with), and 404 if the person UUID is not visible to your tenant.entity_uuids, which reports only the memberships this call removed. Returns 404 when the list doesn't exist for the API key's tenant, and 409 when the list's membership is rule-owned (a dynamic list whose members Rings updates automatically, which would restore the entity).DELETE /v1/opportunities/{uuid}/associations/{association_uuid} calls. Name each link by {opportunity_uuid, association_uuid}, or by {opportunity_uuid, entity_uuid, entity_type} when you know the linked record but not the association uuid. Results come back in input order with a status: REMOVED, NOT_FOUND (no such link on that opportunity — nothing deleted) or ERROR (error says why). One bad item never fails the others. Removing a pinned link does not pin another. Returns 400 for an empty list or more than 100 items.emailBody resolver core (undecorated) so access control (grant ownership + explicit EmailAccessPermission) is enforced in exactly one place. Returns: - 404 when the activity / message can't be resolved for the caller - 403 when the caller has no active grant for the activity's ownertenant_uuid) to prevent cross-tenant thread spills. Only EMAIL activities are returned — calendar events share thread_id=None semantics and would muddy the result set. The response is metadata-only. Clients render a thread by fetching this list and then calling /activities/{id}/body for whichever messages they want to expand.Note.by_uuid (tenant/ring scoped) and fetches the HTML body from S3. Missing S3 objects degrade to the stored DB excerpt rather than erroring — matches GraphQL parity.from_stage -> to_stage, the timestamp it happened (entered_at), when the opportunity left that stage again (exited_at, null while it is still there), and how long it sat there (days_in_stage). The first transition of an opportunity has a null from_stage — that is the opportunity being created in its initial stage, not a move out of an unknown stage. Use this to answer pipeline-velocity questions ("how long did this deal spend in each stage", "where did it stall") that the point-in-time days_in_stage on GET /v1/opportunities/{uuid} can't. Stage names are resolved for stages that still exist in the tenant; a stage deleted since the transition returns its UUID with a null name. The full stage list for a type is available from GET /v1/opportunity-types/{type_uuid}/stages. Works for sub-opportunities as well as top-level ones. Returns 404 if the opportunity is not visible in the tenant's ring.company_custom_fields, keyed by each field's key (e.g. sector_1697011571764_3), not its uuid. Use this to find out what each key means — its display name, type and, for selects, the allowed choices — and to find the key to write a value under. Definitions rarely change, so fetch them once rather than per company.GET /v1/companies?uuids=... or GET /v1/persons?uuids=... to hydrate the returned UUIDs into full records, depending on the list's kind (from GET /v1/lists). city, state and country filter members by where they are based — "who on this list is in LA" in one call, instead of paging every member and hydrating each one to check. They AND together. Members with no location on record are excluded when a location filter is set.is_positive marks outcomes that count as a successful intro in /v1/intros/insights. Outcome and source type values here are what outcome and source_type accept on the other intro endpoints.entity_uuid + order=asc), every intro a person made for the firm (introducer_uuid), or intros still waiting on an outcome (has_outcome=false). Resolve names to UUIDs first with the lookup endpoints; there is no free-text search here.entity_uuid + entity_type: notes associated with that entity (either via the generic associations table or, for PERSON/COMPANY, the legacy rings_*_uuid columns). - search: full-text match over search_vector. - Standard pagination, sort_by, order. Body content is intentionally omitted from the list view; fetch individual notes to get the HTML. The excerpt field is always populated from the cheap DB column.type.uuid on existing records) - opportunity_stage_uuid: Filter by stage UUID Sort parameters: - sort_by: name | created_at | updated_at | value | weighted_value | probability | source_date | intro_date | last_activity_date - order: asc | desc (default: asc) Pagination: - page: Page number (default: 1) - per_page: Results per page (default: 50)group_by=STAGE omits stages that hold no opportunities, and without a type filter a tenant can have more stages than one page holds, so check total. Filtering uses the same parameters as GET /v1/opportunities, so the same filters describe the same set of opportunities on both endpoints — except that the grouped rollup excludes opportunities sitting in a hidden stage (a stage toggled off in the stage editor), which the flat list still returns. For a pipeline breakdown, pass the parent opportunity's UUID as parent_uuid. Sorting: - sort_by: count | total_value | total_weighted_value | average_probability | group_label. Defaults to count, descending. - order: asc | desc Per group the response carries count, total_value, total_weighted_value, average_probability, and the children_* rollup of sub-opportunities. average_days_in_stage and stalled_count are only computed when include_stage_metrics=true, since computing them across the whole filtered set is slower. Two caveats worth knowing: - COMPANY, PERSON, and ASSOCIATIONS grouping fan a single opportunity out across every entity it is associated with, so per-group sums double-count and the children_* fields stay 0 to avoid compounding it. - total, page, and per_page describe groups, not opportunities, while total_value/total_weighted_value at the top level are deduplicated sums across the whole filtered set.uuid of a field before setting it on create or update, including fields no opportunity has a value for yet. Each field has its uuid, key, display name, type and, for selects, the allowed choices. standard_key_to_uuid maps the 16 standard web-app column keys (e.g. targetSize) to this tenant's field uuids. Definitions rarely change, so fetch them once rather than on every opportunity read.stage_uuid before creating an opportunity. Unlike grouping by stage, this lists every stage of the type, including ones no opportunity is in yet. A name can be in the PARENT set (the default) or the CHILD set, so check both before concluding it doesn't exist. A stage is only valid when paired with the type_uuid it belongs to.type_uuid, both before creating an opportunity and before filtering by opportunity_type_uuid. Match label/label_plural for top-level opportunities and sub_label/sub_label_plural for that type's sub-opportunities (e.g. "LPs"). A sub-label match is this type's uuid with hierarchy_type=CHILD. When the user also names the parent, resolve that parent with an opportunity lookup and pass its opportunity uuid as parent_uuid. Each item's pinning_config declares whether a person/company association is required for that type. To get the matching stage_uuid, call GET /v1/opportunity-types/{type_uuid}/stages.id in custom_fields on GET /v1/persons and GET /v1/persons/{person_uuid}, and written through custom_fields on POST /v1/persons and PUT /v1/persons/{person_uuid}. type is one of CURRENCY, DECIMAL, PERCENTAGE, SINGLE_SELECTION, MULTI_SELECTION, TEXT, LONG_TEXT or DATE; selection fields list their options. Fields come back in the order the tenant arranged them.limit matches (default 5, max 20), ranked so tenant-specific records appear before global ones. Parameters: - email: Exact email address match (primary or secondary emails) - linkedin_url: LinkedIn profile URL (e.g. 'https://linkedin.com/in/johndoe') - domain: Email domain to match — e.g. 'acme.com' matches 'alice@acme.com' - limit: Max results to return (default: 5, max: 20)entity_kind = the list's own kind — a list is single-kind, so no per-entity kind is accepted. Idempotent: a UUID that is already a member is left untouched and omitted from the response's entity_uuids, which reports only the memberships this call created. Returns 404 when the list doesn't exist for the API key's tenant, and 409 when the list's membership is rule-owned (a dynamic list whose members Rings updates automatically, which would undo a manual edit).addAssociation with parent_entity_type=NOTE. Idempotent on duplicates: if the association already exists the existing row is returned (still status 201 for client simplicity — matches the opportunity association sub-endpoint).addEntityToOpportunity: rejects with 404 when the opportunity or target entity doesn't exist for this tenant. Idempotent on duplicate associations — returns the existing association row rather than erroring.POST /v1/opportunities/{uuid}/associations calls whenever 2+ links are needed — e.g. attaching each sub-opportunity's company, or adding several people to one deal. Each item is {opportunity_uuid, entity_uuid, entity_type, context?}. Items are applied independently and returned in input order with a status: CREATED (new link), EXISTS (already linked — nothing written) or ERROR (error says why, e.g. opportunity or target not found). One bad item never fails the others. The first PERSON / COMPANY added to an opportunity with none pinned is auto-pinned, as in the single endpoint. Returns 400 for an empty list or more than 100 items.uuids (company UUIDs, max 100), plus the same optional periods group_by, and sort_by controls as the single-entity endpoint. results holds one entry per input UUID, in order. UUIDs with no data — including UUIDs outside the caller's tenant — come back with empty periods rather than failing the whole batch.uuids (company UUIDs, max 100) plus optional limit (top contacts per target, default 25, max 100), sort_by, and order. results holds one entry per input UUID, in order; each carries that target's total and its top contacts. Targets that are not found come back with empty items rather than failing the whole batch.uuids (person UUIDs, max 100), plus the same optional periods and group_by controls as the single-entity endpoint. results holds one entry per input UUID, in order. UUIDs with no data — or that the identified user is not authorized to read — come back with empty periods rather than failing the whole batch.uuids (person UUIDs, max 100) plus optional limit (top contacts per target, default 25, max 100), sort_by, and order. results holds one entry per input UUID, in order; each carries that target's total and its top contacts. Targets that are not found come back with empty items rather than failing the whole batch.linkedin_url or website matches an existing tenant-visible company (incl. Crunchbase / Rings Research globals), that record's UUID is reused so the tenant row converges onto the canonical entity instead of forking a duplicate. If no match is found, the downstream write generates a fresh UUID.entity_uuids, if provided, are added as members at creation time (same entity_kind as the list's kind). List names are unique per tenant per kind — returns 409 if name is already in use for that kind.createNote resolver: full HTML content goes to S3 under the new note UUID, a plain-text excerpt is persisted on the DB row. associations are materialised via the shared create_associations path, silently ignoring entities that can't be resolved for the caller's tenant.POST /v1/notes (full body, excerpt, owner and inline associations). A single failing row comes back as status ERROR without sinking the rest of the batch; results are returned in input order. Supplying idempotency_key per note makes the import safely re-runnable: re-submitting the same key returns the existing note (status SKIPPED) instead of creating a duplicate. This is the note half of the bulk import flow — create the people first via POST /v1/persons/batch, then attach their notes here. A person/company created moments earlier may not yet be visible to this endpoint — newly written records become readable after a short sync delay. If a note's association can't be resolved, that note is NOT created and is returned with status ERROR and a retryable message — wait briefly for the entity to sync, then re-submit the same idempotency_key.POST /v1/opportunities calls whenever 2+ records are created, e.g. importing deals from a spreadsheet or adding attendee sub-opportunities under an event. Each item takes the same fields as the single-record endpoint: name, type_uuid and stage_uuid required; associations, custom_fields, parent_uuid, owner_user_uuid and the rest optional. Resolve the type and stage once (GET /v1/opportunity-types and GET /v1/opportunity-types/{type_uuid}/stages) and companies/people with the lookup batch endpoints before calling this. Items are created in order, each on its own, and returned in input order with a status: CREATED with the new uuid, or ERROR with the message the single endpoint would have returned (e.g. "Unknown stage_uuid"). One bad item never fails the others and leaves no row behind. Never resend an item that came back CREATED; that makes a duplicate. A sub-opportunity needs an existing parent: create the parent first and pass its uuid as parent_uuid. A person or company created moments ago may come back as "Association target not found"; retry just those items shortly. Returns 400 for an empty list or more than 100 items.email matches an existing person in the tenant, the existing record is updated in place and its UUID is returned (deduplication by email). Returns the UUID and rings_updated_at timestamp on success.email or linkedin_url matches an existing tenant-visible person, that person is returned (status MATCHED) without a write; otherwise a new tenant-source record is created (status CREATED). This mirrors the matching of GET /v1/persons/lookup and the dedupe of POST /v1/persons — the per-item create runs the same upsert_person path, so a batch entry behaves exactly like a single create. Items are processed independently — a single failing row comes back as status ERROR without sinking the rest of the batch. Results are returned in input order. Duplicate identifiers within the same batch resolve to one record: a later row sharing an email/linkedin_url with an earlier row comes back MATCHED against the first row's UUID rather than creating a second person (an earlier create isn't yet visible to the matcher mid-batch). This is the bulk primitive for an import flow: look people up first (POST /v1/persons/lookup/batch), then create the unmatched remainder here, then attach notes (POST /v1/notes/batch).introducer; introduced_to defaults to you. Outbound (the firm made an intro): pass introduced_to; introducer defaults to you. Every party must be an existing record. Returns 201 with the new intro, or 200 with duplicate: true and the existing intro when one with the same direction, date and parties is already logged, so retrying is safe. The response is the saved intro; do not re-fetch it to confirm.queries is resolved independently using the same matching semantics as GET /v1/companies/lookup. Results are returned in the same order as the input.queries is matched independently using the same semantics as GET /v1/persons/lookup (email, linkedin_url, or domain match). Results are returned in the same order as the input queries. Entries with no matching criteria return empty matches rather than an error.current_company on person read endpoints is derived from. Replaces any existing primary affiliation for this tenant's ring outright -- this is not additive (a person has one current company per ring). Returns 404 if either the person or the company UUID is not visible to your tenant.{"outcome": "Meeting held"}. Sending introducer, introduced or introduced_to replaces the parties in that role only. outcome: null sets the intro back to pending. Intros found in an email you can't access return 404.content is provided the S3 object is re-uploaded and the excerpt on the DB row is refreshed; omitting content leaves the stored HTML untouched. 404s when the note isn't visible for the caller's tenant.PUT /v1/opportunities/{uuid} calls whenever 2+ records change — e.g. setting a different stage, value, next step or custom field on each sub-opportunity from a spreadsheet. Each item is {uuid, ...fields} and takes the same fields as the single-record endpoint; only the fields sent are written. Items are applied in order, each on its own, and returned in input order with a status: UPDATED, or ERROR with the message the single endpoint would have returned (e.g. "Opportunity not found", "Unknown stage_uuid"). One bad item never fails the others. Returns 400 for an empty list or more than 100 items.Click any prompt to copy it.