Skip to main content

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.
Activity ingestion feeds directly into lead scoring: a standard activity always enqueues its matched contact for rescoring, and a contact-scoped custom activity enqueues every contact it linked. Company-scoped custom activities don’t feed scoring at all. In every case, this is a no-op if scoring isn’t enabled for your workspace, or if the activity didn’t end up linked to any contact.

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:
or, if the source exists but isn’t enabled for that activity type:
The Activities & Scoring page will hand you a ready-to-use webhook URL and a sample payload for whichever activity type you’re binding a source to — useful for confirming the exact endpoint and field names before you write code against them.
All endpoints below sit under the same base path and authentication as the rest of the public API — see API Introduction. Activities endpoints only exist at /api/public/activities/... — there is no v2 equivalent.

List custom activity types

Returns every custom activity type defined for your workspace. This only returns activity types created through this API or the Activities & Scoring page (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:
Returns 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

occured_at is spelled exactly like that in the real API — it’s missing the second “r.” This isn’t a typo in this doc; sending the correctly-spelled occurred_at will fail validation because the field the API actually reads is occured_at.
Returns 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

This is the same path as Log a custom activity above, but PATCH on it edits the activity type definition, not an individual logged activity instance. There is no endpoint to edit an already-logged activity instance.
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

Returns 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:
This 409 fires as soon as a single activity has ever been logged against the type — including one that ended up unlinked (see Log a custom activity above), since it’s still a row referencing this type. The public API has no endpoint to delete a logged activity instance, so once a type has been used even once, it can no longer be deleted through this API — deletion only works for a type nothing has ever been logged against. On a successful delete, RevyOps also strips this type’s binding from any source configured under Activities & Scoring, so a source that used to feed this type won’t silently reference a dangling ID.

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:
There’s no 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:
A recognized field with an invalid value (e.g. direction: "sideways") returns 400 with a standard field-keyed validation error. On success, every endpoint below returns 201 with {"id": <activity id>}.

Log a call

Log an email

Log a LinkedIn message

Log an SMS message

Log a WhatsApp message

Same shape as SMS above — WhatsApp and SMS share an identical properties schema.