Overview
An activity is a timestamped event on a contact’s or company’s timeline — a call, an email, a LinkedIn message, or something specific to your business like “Demo Booked” or “Contract Signed.” The Activities API supports two different kinds of activity, and picking the right one matters:Standard activities
Five fixed, built-in channels:
call, email, linkedin_message, sms, whatsapp. Logging one writes directly into the same core tables (Calls, Emails, LinkedInMessages, SMSMessages, WhatsAppMessages) that RevyOps’s own outreach integrations populate. Use these for real communication events you’re capturing outside of a connected integration.Custom activities
Activity types you define yourself — a name, which object they apply to (contact or company), and an optional field schema. This is the same generic mechanism RevyOps uses internally to mirror HubSpot and Close CRM activity types. Use these for milestones and events that don’t fit one of the five standard channels.
The source field
Every logging endpoint accepts an optional source field. If you omit it (or send "revyops"), the activity is attributed to the built-in revyops origin and no extra setup is needed.
If you pass anything else, that value must already be registered as a source under Settings → Activities & Scoring in the RevyOps dashboard, and it must be explicitly enabled for the specific activity type you’re logging — standard activities are bound per channel (e.g. call), custom activities are bound per activity type. An unregistered or unbound source returns a 422:
/api/public/activities/... — there is no v2 equivalent.
List custom activity types
origin: "revyops") — it does not include the activity types RevyOps auto-creates to mirror synced HubSpot or Close CRM activities.
The
id field here is the value you pass as activity_key in every other endpoint on this page. It’s a server-generated UUID, not a sequential database ID — there’s no way to choose your own key.Create a custom activity type
Each entry in
schema describes one property you’ll be able to attach when logging an instance of this activity:
201 with the created type in the same shape as the list endpoint, including the generated id (your activity_key going forward). A malformed payload — missing name, or a type outside the allowed set — returns 400 with a field-keyed validation error.
Log a custom activity
201 with {"id": <activity id>} on success.
Property validation. Any key in properties that isn’t in the activity type’s schema returns 422 with an invalid_fields list. Keys that are in the schema are validated by their declared type: number values must parse as numeric, date values must be YYYY-MM-DD, dropdown_singleselect values must be one of available_options (if any are defined), and dropdown_multiselect values must be a list of strings, each one of available_options (if any are defined). A blank or null value skips type validation entirely. Type mismatches return 400, not 422.
Linking behavior. For a contact-scoped type, RevyOps checks email, linkedin_url, and phone independently and links every one that matches an existing contact — so if email matches one contact and phone matches a different one, the activity can end up linked to more than one contact. (This is different from standard activities below, where only a single contact is ever matched.) For a company-scoped type, only domain is checked, against Companies.domain (also lowercased and trimmed before matching).
If none of the identifiers you send match an existing contact or company — or you don’t send any — the activity is still created and returns 201. It’s just left unlinked: it exists in your workspace but won’t appear on any specific contact’s or company’s timeline, and no error is raised.
Email is lowercased and trimmed before matching. linkedin_url is normalized to its canonical linkedin.com/in/<handle> form. phone is parsed to E.164; if it can’t be parsed, it’s silently dropped from the match attempt rather than causing an error.
Update a custom activity type
Only the fields you include are changed. Returns
200 with the updated type object, or 404 if activity_key doesn’t match an existing type.
Renaming a type also renames any scoring rule in Activities & Scoring that’s attached to it.
Delete a custom activity type
204 on success, or 404 if activity_key doesn’t match an existing type. Returns 409 if any activities have already been logged against this type:
Standard activities: shared request shape
The five endpoints below (call, email, linkedin_message, sms, whatsapp) all share the same top-level fields — only properties differs per channel, documented in each section:
*
properties isn’t required by name, but every channel has at least one required field inside it (e.g. direction for calls), so omitting properties entirely still fails validation on that field. At least one of email/linkedin_url/phone that resolves to an existing contact is also effectively required — see below.
Standard activities never create a contact. RevyOps checks email first, then linkedin_url, then phone, and links to the first one that matches an existing contact — unlike custom activities, only ever one contact is linked. If none of the identifiers you send match an existing contact, the request fails with 422:
domain field on these endpoints — the activity’s company is set automatically from the matched contact’s company.
Any key inside properties that isn’t in that channel’s table below returns 422:
direction: "sideways") returns 400 with a standard field-keyed validation error. On success, every endpoint below returns 201 with {"id": <activity id>}.