Restricted access. Public API v2 is currently available only to allowlisted
organizations. Requests from workspaces that have not been enabled are rejected.
Contact your Reevo representative to have your organization added to the allowlist.
Manage Relationships
Use relationship endpoints when you need to list, add, update, or remove links between records.
/api/v2/public/{record_type}/{record_id}/relationship/{api_name}
Direct vs junction relationships
Every relationship is one of two kinds, reported by the type field on each entry in a record type’s _schema response. The kind decides which operations apply to its edges:
- Direct — a single outbound reference, such as a foreign key or an owned association. There is no edge record between the two sides, so a direct relationship has no per-edge attributes: you add or remove the reference, but there is nothing to
PATCH.
- Junction — a many-to-many link mediated by a junction object that carries per-edge attributes of its own, such as
role or is_primary. Junctions are the only relationships with an editable edge.
| Operation | Direct | Junction |
|---|
List (GET) | Yes | Yes |
Add (POST) | Yes | Yes |
Remove (DELETE) | Yes | Yes |
Update edge (PATCH) | No | Yes |
Read a relationship’s type from GET /_schema/{record_type} to tell the two apart. The typed junction relationships on this page — a contact’s accounts and opportunities, and an account’s or opportunity’s contacts — are the only ones that expose a per-edge PATCH.
Read a relationship
curl "https://api.reevo.ai/api/v2/public/contact/<contact_id>/relationship/accounts" \
-H "Authorization: Bearer <your-api-key>"
Responses include the parent, relationship name, related items, and pagination metadata.
Add a relationship
Provide exactly one of related_record_id or related_record_match. Sending both, or neither, returns field_value_invalid.
Add a target by ID:
{
"related_record_id": "<account_id>"
}
Or match the target by a unique field. related_record_match takes a { "field", "value" } envelope — the same match shape used by _upsert and reference-field writes — where field is a match key on the target record type:
{
"related_record_match": {
"field": "domains.domain_name",
"value": "acme.com"
}
}
related_record_match is useful when an upstream system knows a unique attribute of the target, such as an account domain, but not the Reevo record ID. The allowed field values depend on the target record type, not on the junction:
| Target record type | Allowed field match keys |
|---|
account | domains.domain_name |
contact | primary_email, contact_emails.email |
opportunity | None — address by related_record_id |
| Custom objects and generic relationships | None — address by related_record_id |
Every record type is always addressable by ID through related_record_id. A match must resolve to exactly one record: an unsupported field returns invalid_matching_field, a value that matches more than one record returns ambiguous_match, and a value that matches nothing returns target_record_not_found.
Linking a contact to an opportunity has a precondition. The contact must already be associated with that opportunity’s account. If it is not, the add returns 409 contact_account_association_required — link the contact to the account first, then add the contact-to-opportunity edge.
Update a relationship edge
PATCH applies only to junction relationships — the ones whose edge carries its own attributes. It updates those per-edge attributes in place (for example a contact-on-account edge’s role and is_primary) without unlinking and re-adding the target:
curl -X PATCH "https://api.reevo.ai/api/v2/public/contact/<contact_id>/relationship/accounts/<target_id>" \
-H "Authorization: Bearer <your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"role": "CTO",
"is_primary": true
}'
Direct relationships have no edge attributes, so there is nothing to PATCH, and the generic /{record_type}/{id}/relationship/{api_name} surface exposes only GET, POST, and DELETE. To change a direct link, remove and re-add it.
Remove a relationship
curl -X DELETE "https://api.reevo.ai/api/v2/public/contact/<contact_id>/relationship/accounts/<target_id>" \
-H "Authorization: Bearer <your-api-key>"
Edge DELETE is idempotent for absent targets and non-primary edges: it returns 204 No Content whether or not such an edge existed. Deleting an absent, already-removed, or non-existent target still returns 204 and never returns target_record_not_found, so for these cases the status alone cannot tell you whether an edge was actually removed.
One exception: you cannot delete the primary contact on a contact-to-opportunity edge. DELETE of that edge returns 400 validation_error — reassign the opportunity’s primary contact to another contact first, then remove the edge.
Relationship support by resource
| Resource | Relationship support |
|---|
contact | Typed accounts, typed opportunities, plus generic relationship names |
account | Typed contacts, plus generic relationship names |
opportunity | Typed contacts, plus generic relationship names |
task | Generic relationship names |
note | Generic relationship names |
meeting | Generic relationship names; the meeting record itself is read-only. Relationship-edge writes require a meeting write scope — see Meeting relationship edges |
| Custom objects | Generic relationship names |
Meeting relationship edges
A meeting record is read-only, but its relationship edges are governed by your key’s meeting scope, not by the record’s writability. Every edge POST/DELETE on a meeting requires a meeting write scope: with a read-only meeting key the scope check runs before relationship validation and returns 403 insufficient_scope for any edge write — even for a relationship name that does not exist (you get 403, not 404 unknown_relationship).
GET /api/v2/public/_schema/meeting is the source of truth for which meeting relationships are writable; today it marks meeting’s relationships non-creatable and non-updatable. Confirm writability there and ensure your key carries the meeting write scope before adding or removing meeting edges.
Cardinality and junction records
Typed relationships such as contacts on accounts and contacts on opportunities are many-to-many: a contact can link to many accounts, and an account can link to many contacts. The link itself can carry data, stored on a junction record. For example, a contact-on-account edge can hold a role and an is_primary flag, while a contact-on-opportunity edge holds only is_primary.
Use this to decide how to change an edge:
- If the relationship has its own attributes (a junction edge), update those attributes with a per-edge
PATCH, as shown above.
- If the relationship is a plain link with no edge attributes, there is nothing to patch. Remove and re-add the edge to change it.
Primary edges and is_primary
is_primary is scoped differently for the two typed junctions, so the side that holds the “at most one primary” constraint depends on the relationship.
Contact-to-account is contact-scoped. Here is_primary means “this is the contact’s primary account,” and a contact has at most one primary account. Promoting a different account edge to primary demotes the previous one, and demoting the current primary by patching is_primary: false auto-promotes one of the contact’s sibling account edges so the contact still has a primary — do not assume a demote leaves the contact with no primary. Because the flag belongs to the contact, listing from the account side can legitimately show several is_primary: true rows: GET /account/{id}/relationship/contacts returns one row per linked contact, and each contact may have flagged this account as its primary. Multiple is_primary: true rows on the account side are expected, not corruption.
Contact-to-opportunity is opportunity-scoped. Here is_primary means “this is the opportunity’s primary contact,” and an opportunity has at most one primary contact; promoting a different contact to primary demotes the previous one. A single contact can be the primary contact on many opportunities, so listing from the contact side (GET /contact/{id}/relationship/opportunities) can legitimately show several is_primary: true rows — one per opportunity where this contact is primary. Multiple is_primary: true rows on the contact side are expected, not corruption.
You do not address junction records directly. Reevo’s canonical junction record types, such as contact_account_role and contact_opportunity_role, are not exposed as top-level Public API v2 records. Manage them through the parent-scoped relationship endpoints on this page. See Not Supported Yet for the canonical junction endpoints that are intentionally not available.
Common errors
| Error code | Usually means | How to recover |
|---|
unknown_relationship | Relationship API name is not declared for this record type | Check /_schema/{record_type} |
target_record_not_found | On add, the target record ID or match value did not resolve. Edge DELETE is idempotent and never returns this — see Remove a relationship. | Re-query or create the target first |
record_not_found | On a typed-junction PATCH, the edge could not be resolved (returned as 404, not target_record_not_found) | Re-query the edge and confirm the target_id in the PATCH path |
invalid_matching_field | The related_record_match field is not an allowed match key for the target record type | Use an allowed match key for the target type (see Add a relationship), or address the target by related_record_id |
ambiguous_match | The related_record_match value matched more than one record | Narrow to a unique value, resolve duplicate records, or address the target by related_record_id |
field_value_invalid | The request sent both related_record_id and related_record_match, or neither | Send exactly one target reference on the request |
contact_account_association_required | Adding a contact-to-opportunity edge while the contact is not yet associated with the opportunity’s account | Link the contact to the opportunity’s account first, then retry the contact-to-opportunity add |
field_not_writable | The relationship maps to a read-only or singleton field that cannot be changed through this path | Use the supported parent field or endpoint |
insufficient_scope | Your API key lacks the scope this edge write needs — for example, a meeting edge POST/DELETE with a read-only meeting key returns 403 | Use a key that carries the required write scope |