{"openapi":"3.0.3","info":{"title":"DealNav External API","version":"1.0","description":"DealNav's external API. Every request needs `Authorization: Bearer dlnv_<prefix>_<secret>`. Changes need a Read and write key."},"servers":[{"url":"https://deal-nav.com/dealnav-api/api/ext"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Use `Authorization: Bearer dlnv_<prefix>_<secret>`."}},"schemas":{"Error":{"type":"object","required":["code","message","requestId"],"properties":{"code":{"type":"string"},"message":{"type":"string"},"requestId":{"type":"string"}}},"Location":{"type":"object","description":"Resolved address components (populated by geocoding).","properties":{"latitude":{"type":"string","nullable":true},"longitude":{"type":"string","nullable":true},"streetNumber":{"type":"string","nullable":true},"streetName":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"postalCode":{"type":"string","nullable":true},"county":{"type":"string","nullable":true}}},"Property":{"type":"object","description":"Property (a.k.a. deal). Returned by the property endpoints.","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"name":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"units":{"type":"string","nullable":true},"lotSize":{"type":"string","nullable":true},"ownerName":{"type":"string","nullable":true},"ownerMailingAddress":{"type":"object","description":"The owner's mailing address, frequently not the property's own address.","properties":{"address":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"postalCode":{"type":"string","nullable":true}}},"email":{"type":"string","nullable":true},"phoneNumber":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"priority":{"type":"string","nullable":true},"category":{"type":"string","nullable":true,"description":"Lead or Deal."},"location":{"type":"object","description":"Resolved address components (populated by geocoding).","properties":{"latitude":{"type":"string","nullable":true},"longitude":{"type":"string","nullable":true},"streetNumber":{"type":"string","nullable":true},"streetName":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"postalCode":{"type":"string","nullable":true},"county":{"type":"string","nullable":true}}},"squareFeet":{"type":"string","nullable":true},"propertyType":{"type":"string","nullable":true},"tags":{"type":"array","items":{"type":"string"}},"yearBuilt":{"type":"string","nullable":true},"apn":{"type":"string","nullable":true,"description":"Assessor parcel number."},"propertySubtype":{"type":"string","nullable":true},"lastSalePrice":{"type":"string","nullable":true,"description":"DECIMAL(15,2), serialized as a string to avoid float rounding."},"lastSaleDate":{"type":"string","format":"date","nullable":true},"source":{"type":"string","nullable":true,"description":"Originating system for this record."},"createdAt":{"type":"string","format":"date-time","nullable":true},"url":{"type":"string","nullable":true,"description":"Deep link to this record's page in the DealNav web app."},"customFields":{"type":"array","items":{"$ref":"#/components/schemas/CustomFieldValue"},"description":"Active custom field definitions for this entity, with the stored value or null."}}},"Contact":{"type":"object","description":"Contact. Returned by the contact endpoints.","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"firstName":{"type":"string","nullable":true},"lastName":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"type":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"companyName":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"priority":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"source":{"type":"string","nullable":true,"description":"Lead source: where this contact came from."},"tags":{"type":"array","items":{"type":"string"}},"address":{"type":"string","nullable":true},"location":{"type":"object","description":"Resolved address components (populated by geocoding).","properties":{"latitude":{"type":"string","nullable":true},"longitude":{"type":"string","nullable":true},"streetNumber":{"type":"string","nullable":true},"streetName":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"postalCode":{"type":"string","nullable":true},"county":{"type":"string","nullable":true}}},"createdAt":{"type":"string","format":"date-time","nullable":true},"url":{"type":"string","nullable":true,"description":"Deep link to this record's page in the DealNav web app."},"customFields":{"type":"array","items":{"$ref":"#/components/schemas/CustomFieldValue"},"description":"Active custom field definitions for this entity, with the stored value or null."}}},"ContactInfoValue":{"type":"object","description":"One entry from a contact's multi-value email or phone list.","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"value":{"type":"string"},"label":{"type":"string","nullable":true,"description":"e.g. Mobile, Office."},"ext":{"type":"string","nullable":true},"isPrimary":{"type":"boolean"}}},"ContactDetail":{"allOf":[{"$ref":"#/components/schemas/Contact"},{"type":"object","description":"Returned by `GET /contacts/{id}` only. The list endpoints return the base Contact without these arrays.","properties":{"emails":{"type":"array","items":{"$ref":"#/components/schemas/ContactInfoValue"}},"phones":{"type":"array","items":{"$ref":"#/components/schemas/ContactInfoValue"}}}}]},"Activity":{"type":"object","description":"A logged interaction with a contact — a call, email, meeting, or text.","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"type":{"type":"string","enum":["call","email","meeting","sms","other"]},"direction":{"type":"string","nullable":true,"enum":["inbound","outbound",null]},"occurredAt":{"type":"string","format":"date-time"},"durationSeconds":{"type":"integer","nullable":true},"disposition":{"type":"string","nullable":true},"subject":{"type":"string","nullable":true},"body":{"type":"string","nullable":true},"source":{"type":"string","nullable":true},"externalId":{"type":"string","nullable":true},"contactId":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string).","nullable":true},"propertyId":{"type":"string","description":"Optional linked deal. Set when logging an activity that also belongs on the deal timeline.","nullable":true},"createdBy":{"type":"integer","description":"User ID that owns the API key."},"createdAt":{"type":"string","format":"date-time"}}},"ActivityCreate":{"type":"object","required":["type"],"description":"A logged interaction. `occurredAt` defaults to now and cannot be in the future.\n\nPass `source` plus `externalId` to make the write idempotent: the pair is unique per dataset, so a retried delivery gets a `409` instead of a duplicate call record. The `409` body's `details.activityId` is the activity that already exists.","properties":{"type":{"type":"string","enum":["call","email","meeting","sms","other"]},"direction":{"type":"string","enum":["inbound","outbound"]},"occurredAt":{"type":"string","format":"date-time"},"durationSeconds":{"type":"integer","minimum":0,"maximum":86400},"disposition":{"type":"string","maxLength":100,"description":"e.g. Connected, Voicemail, No answer."},"subject":{"type":"string","maxLength":255},"body":{"type":"string","maxLength":10000},"source":{"type":"string","maxLength":50,"description":"Originating system, e.g. the dialer's name."},"externalId":{"type":"string","maxLength":255,"description":"The originating system's own ID for this interaction."},"propertyId":{"type":"integer","minimum":1,"description":"Optional deal in this dataset. When set, the activity is also written to that deal's in-app timeline."}}},"PropertyContactLink":{"type":"object","description":"An association between a property and a contact.","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"propertyId":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"contactId":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"isPrimary":{"type":"boolean","description":"The property's primary contact. At most one per property."},"createdAt":{"type":"string","format":"date-time"},"propertyUrl":{"type":"string","nullable":true},"contactUrl":{"type":"string","nullable":true}}},"PropertyContactLinkCreate":{"type":"object","required":["propertyId","contactId"],"description":"Links an existing property to an existing contact. Both must already be in the dataset — this endpoint never creates either side.\n\nRe-linking a pair that is already linked returns `409` with the existing `details.linkId` rather than stacking a duplicate. Setting `isPrimary` demotes the property's current primary contact.","properties":{"propertyId":{"type":"integer","minimum":1},"contactId":{"type":"integer","minimum":1},"isPrimary":{"type":"boolean","default":false}}},"ContactMatch":{"allOf":[{"$ref":"#/components/schemas/ContactDetail"},{"type":"object","properties":{"name":{"type":"string","nullable":true,"description":"Display name: the person name, or the company for company-only and entity contacts; null when the contact has neither."},"mailingAddress":{"type":"object","properties":{"address":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"postalCode":{"type":"string","nullable":true},"county":{"type":"string","nullable":true}}},"owner":{"type":"object","nullable":true,"description":"The user who created the record.","properties":{"id":{"type":"string"},"name":{"type":"string","nullable":true}}},"matchedFields":{"type":"array","items":{"type":"string"},"description":"Which criteria this record matched on. More fields means a stronger match."}}}]},"PropertyMatch":{"allOf":[{"$ref":"#/components/schemas/Property"},{"type":"object","properties":{"matchedFields":{"type":"array","items":{"type":"string"},"description":"Which criteria this record matched on. More fields means a stronger match."}}}]},"ContactSearch":{"type":"object","description":"Criteria for finding possible duplicate contacts. At least one usable criterion is required; supplying none returns `400` rather than every contact.\n\nCriteria are OR-ed — any single one matching makes a record a candidate — and `matchedFields` on each result reports which ones matched, so the caller decides what counts as the same person.\n\nEmail comparison is case-insensitive. Phone comparison is on the last 10 digits, so formatting and country code don't matter; a value with fewer than 7 digits is ignored as too weak. Names are compared after case and punctuation normalization: when both `firstName` and `lastName` are supplied and both match, `name` appears in `matchedFields`; a single name (or only one side of a full name matching) is a weaker signal reported as `firstName` or `lastName`. Company names are compared the same way, and `&` counts as punctuation, so `Smith & Sons, LLC` matches `smith sons llc` but not `Smith and Sons LLC`. Addresses match on the normalized street line.\n\nPhones and emails held in a contact's multi-value lists are searched too, not just the primary.","properties":{"email":{"type":"string","maxLength":300},"phone":{"type":"string","maxLength":300},"firstName":{"type":"string","maxLength":255},"lastName":{"type":"string","maxLength":255},"companyName":{"type":"string","maxLength":255},"address":{"type":"string","maxLength":255},"limit":{"type":"integer","minimum":1,"maximum":100,"default":25}}},"PropertySearch":{"type":"object","description":"Criteria for finding possible duplicate properties. At least one of `address`, `apn`, or a `latitude`/`longitude` pair is required.\n\n`apn` is compared with all separators stripped, so `123-45-678` matches `12345678`.\n\n`address` is compared on a normalized street line: case, punctuation, street-suffix abbreviations (`Street`/`St`), directional prefixes (`North`/`N`), and unit formatting (`Suite 400`/`#400`) are all absorbed. A unit is split from the street address rather than dropped — the street matching is building-level, and `unit` appears in `matchedFields` only when both sides carry the same one. Anything after the first comma is treated as locality and ignored, so a one-line address matches a record whose city and state live in their own columns.\n\n`latitude`/`longitude` must be supplied together and match any property within `radiusMeters`.\n\n`city`, `state`, and `postalCode` never make a record a match on their own — a shared ZIP is not a duplicate. They corroborate a match found another way and appear in `matchedFields` when they agree. City comparison drops a leading `the` or `city of`, so Bronx matches The Bronx. A mismatching city or state excludes an address-derived match unless that would leave zero matches, in which case the unfiltered street matches are returned.","properties":{"address":{"type":"string","maxLength":255},"city":{"type":"string","maxLength":255},"state":{"type":"string","maxLength":255},"postalCode":{"type":"string","maxLength":255},"apn":{"type":"string","maxLength":100},"latitude":{"type":"string"},"longitude":{"type":"string"},"radiusMeters":{"type":"number","minimum":1,"maximum":5000,"default":100,"description":"Only used with latitude/longitude."},"limit":{"type":"integer","minimum":1,"maximum":100,"default":25}}},"ContactSearchResponse":{"type":"object","required":["matches","total","truncated"],"properties":{"matches":{"type":"array","items":{"$ref":"#/components/schemas/ContactMatch"},"description":"Strongest match first."},"total":{"type":"integer"},"truncated":{"type":"boolean","description":"True when the candidate scan hit its 500-row ceiling and a match may have been missed. Narrow the criteria and search again before concluding the record is new."}}},"PropertySearchResponse":{"type":"object","required":["matches","total","truncated"],"properties":{"matches":{"type":"array","items":{"$ref":"#/components/schemas/PropertyMatch"},"description":"Strongest match first."},"total":{"type":"integer"},"truncated":{"type":"boolean","description":"True when the candidate scan hit its 500-row ceiling and a match may have been missed. Narrow the criteria and search again before concluding the record is new."}}},"DealStatusOption":{"type":"object","properties":{"id":{"type":"string","description":"Numeric id, or default:lead when Lead is the synthetic lead status."},"status":{"type":"string","description":"The exact value to write into a property's `status`."},"sortOrder":{"type":"integer","nullable":true,"description":"Pipeline order, lowest first."},"isDefault":{"type":"boolean","description":"Seeded by DealNav rather than added by the customer."},"color":{"type":"string","nullable":true,"description":"Hex color the app renders this status with, e.g. `#3b82f6`. Null only for rows predating server-side color assignment."}}},"NamedOption":{"type":"object","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"name":{"type":"string"}}},"TypeOption":{"type":"object","properties":{"id":{"type":"string","description":"Numeric id of this dataset's type row."},"name":{"type":"string"},"isDefault":{"type":"boolean"}}},"DealMembership":{"type":"object","description":"A deal on a pipeline status. Summary only; use get_deal for the full record.","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"name":{"type":"string","nullable":true},"address":{"type":"string","nullable":true},"url":{"type":"string","nullable":true}}},"ContactMembership":{"type":"object","description":"A contact of a given type. Summary only; use get_contact for the full record.","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"firstName":{"type":"string","nullable":true},"lastName":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true},"url":{"type":"string","nullable":true}}},"CustomFieldDefinition":{"type":"object","description":"A customer-defined field on properties or contacts.","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"entityType":{"type":"string","enum":["Property","Contact"]},"key":{"type":"string","description":"Stable identifier for the field."},"label":{"type":"string","description":"Human-facing name shown in the app."},"type":{"type":"string","description":"Field data type (text, number, date, dropdown, ...)."},"options":{"type":"array","nullable":true,"items":{"type":"string"},"description":"Allowed values for a dropdown field; null otherwise."},"numberFormat":{"type":"string","nullable":true,"enum":["plain","currency","percent"],"description":"Display format for a number field; null means plain. Values are stored as bare numbers either way."},"required":{"type":"boolean"},"sortOrder":{"type":"integer"}}},"CustomFieldValue":{"type":"object","description":"A custom field definition plus the value stored on this record.","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"key":{"type":"string"},"label":{"type":"string"},"type":{"type":"string"},"value":{"nullable":true,"description":"Stored value, or null when unset."},"required":{"type":"boolean"},"options":{"type":"array","nullable":true,"items":{"type":"string"}},"numberFormat":{"type":"string","nullable":true,"enum":["plain","currency","percent"],"description":"Display format for a number field; null means plain. Values are stored as bare numbers either way."}}},"Reminder":{"type":"object","description":"Reminder attached to a property or contact.","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"name":{"type":"string","nullable":true},"dataPointId":{"type":"integer","nullable":true,"description":"ID of the linked property or contact."},"dataPointType":{"type":"string","nullable":true,"description":"Property or Contact."},"status":{"type":"string","nullable":true},"setDate":{"type":"string","format":"date-time","nullable":true},"endDate":{"type":"string","nullable":true},"recurringCadence":{"type":"string","nullable":true},"alert":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time","nullable":true}}},"PropertyNote":{"type":"object","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"note":{"type":"string"},"createdBy":{"type":"integer","description":"User ID of the author."},"createdDate":{"type":"string","format":"date-time"},"lastUpdate":{"type":"string","format":"date-time"},"isPinned":{"type":"boolean"},"userName":{"type":"string","nullable":true,"description":"Author's display name, when available."},"propertyId":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."}}},"ContactNote":{"type":"object","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"note":{"type":"string"},"createdBy":{"type":"integer","description":"User ID of the author."},"createdDate":{"type":"string","format":"date-time"},"lastUpdate":{"type":"string","format":"date-time"},"isPinned":{"type":"boolean"},"userName":{"type":"string","nullable":true,"description":"Author's display name, when available."},"contactId":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."}}},"Dataset":{"type":"object","properties":{"id":{"type":"string","description":"Numeric ID (BIGINT, serialized as a string)."},"name":{"type":"string"},"createdAt":{"type":"string","format":"date-time","nullable":true}}},"PropertyWrite":{"type":"object","description":"Writable property fields. `name` is required on create. Note: the write field names for address parts are `city`/`state`/`postalCode`/`county` (these differ from the query field names: see /query).\n\n`status` and `propertyCategory` are kept consistent by the server and cannot be set to a contradictory pair. An explicit `status` decides the category: a blank status or `Lead` stores category `Lead`, anything else stores `Deal`. Sending only `category` derives the status instead: `Deal` promotes a lead to the dataset's first stage but leaves an existing deal status untouched. A create that sets neither field becomes a `Lead`. A patch that mentions neither leaves both alone.\n\n`status` must already exist for the dataset (case-insensitive), including Lead. Unknown stages are rejected with the current list. `propertyType` must match a type returned by GET /customizations/property-types and is not created on write. Omitted `priority` on create is stored as `None`.\n\nStatus changes made through this API are recorded in the deal status history that powers the in-app pipeline reports.\n\n`tags` values that the dataset has not seen before are registered against it. Registration happens before the record is written: if it fails, the write is rejected and nothing is saved.","properties":{"name":{"type":"string","maxLength":255},"address":{"type":"string","maxLength":255},"units":{"type":"string","maxLength":25},"lotSize":{"type":"string","maxLength":25},"ownerName":{"type":"string","maxLength":255},"email":{"type":"string","maxLength":255},"phoneNumber":{"type":"string","maxLength":25},"status":{"type":"string","maxLength":255},"priority":{"type":"string","maxLength":25},"city":{"type":"string","maxLength":255},"state":{"type":"string","maxLength":255},"postalCode":{"type":"string","maxLength":255},"county":{"type":"string","maxLength":255},"squareFeet":{"type":"string","maxLength":255},"propertyType":{"type":"string","maxLength":255},"yearBuilt":{"type":"string","maxLength":255},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":50},"category":{"type":"string","enum":["Lead","Deal"]},"apn":{"type":"string","maxLength":100,"description":"Assessor parcel number."},"propertySubtype":{"type":"string","maxLength":255},"lastSalePrice":{"type":"string","maxLength":20,"description":"Numeric string, e.g. \"4200000.00\"."},"lastSaleDate":{"type":"string","format":"date","description":"YYYY-MM-DD. The column is a DATE."},"source":{"type":"string","maxLength":50},"latitude":{"type":"string","description":"Overrides the geocoded value."},"longitude":{"type":"string","description":"Overrides the geocoded value."},"ownerAddress":{"type":"string","maxLength":255},"ownerCity":{"type":"string","maxLength":255},"ownerState":{"type":"string","maxLength":255},"ownerPostalCode":{"type":"string","maxLength":50},"contactIds":{"type":"array","items":{"type":"integer","minimum":1},"maxItems":25,"description":"Contacts to link the new property to. Create-only. Every ID is verified to be in the dataset before the property is written."},"customFields":{"type":"array","maxItems":50,"description":"Values keyed by `id` or `key` from GET /custom-fields. Unknown keys are rejected with the current list.","items":{"type":"object","properties":{"id":{"type":"string","description":"Custom field definition id."},"key":{"type":"string","description":"Stable field key, e.g. noi."},"value":{"nullable":true}}}}}},"ContactWrite":{"type":"object","description":"Writable contact fields. At least one of firstName/lastName/companyName/email is required on create; send only companyName for an entity contact (LLC, trust, estate). `type` is required on create and must match a type returned by GET /customizations/contact-types.\n\nOmitted `priority` on create is stored as `Medium`.\n\n`email` and `phone` set the contact's primary values and add or flag a primary `contacts_info` row. Other email/phone rows are left active.\n\n`tags` values the dataset has not seen before are registered against it, so they become selectable and filterable in the app. Registration happens before the record is written: if it fails, the write is rejected and nothing is saved.","properties":{"firstName":{"type":"string","maxLength":255},"lastName":{"type":"string","maxLength":255},"fullName":{"type":"string","maxLength":511,"description":"Convenience for callers holding one name string; split on the last space. Ignored when firstName or lastName is supplied."},"email":{"type":"string","maxLength":300},"type":{"type":"string","maxLength":125,"description":"Required on create. Contact role, e.g. Broker or Owner."},"phone":{"type":"string","maxLength":300},"phoneType":{"type":"string","maxLength":255,"description":"Label stored against the phone, e.g. Mobile or Office."},"companyName":{"type":"string","maxLength":255},"title":{"type":"string","maxLength":255},"priority":{"type":"string","maxLength":100},"status":{"type":"string","maxLength":100},"source":{"type":"string","maxLength":100,"description":"Lead source."},"ownerUserId":{"type":"integer","minimum":1,"description":"Record owner. Must be a member of the dataset, else 400. Defaults to the user who owns the API key."},"propertyIds":{"type":"array","items":{"type":"integer","minimum":1},"maxItems":25,"description":"Properties to link the new contact to. Create-only. Every ID is verified to be in the dataset before the contact is written, so a bad ID fails the whole request."},"tags":{"type":"array","items":{"type":"string","maxLength":50},"maxItems":50},"address":{"type":"string","maxLength":255},"city":{"type":"string","maxLength":225},"state":{"type":"string","maxLength":225},"postalCode":{"type":"string","maxLength":225},"county":{"type":"string","maxLength":225},"customFields":{"type":"array","maxItems":50,"description":"Values keyed by `id` or `key` from GET /custom-fields. Unknown keys are rejected with the current list.","items":{"type":"object","properties":{"id":{"type":"string","description":"Custom field definition id."},"key":{"type":"string","description":"Stable field key, e.g. referral."},"value":{"nullable":true}}}}}},"ReminderWrite":{"type":"object","description":"Writable reminder fields. `name`, `dataPointId`, `dataPointType`, and `setDate` are required on create. `setDate` is required because the delivery job skips reminders without one, so a reminder created without a schedule would never fire. A date-only value (`YYYY-MM-DD`) is stored as noon UTC (`YYYY-MM-DDT12:00:00.000Z`). `dataPointId` must name a property or contact in the same dataset; one from another dataset is rejected as `404 Not Found`.\n\n`status` defaults to `Upcoming` on create, which is the only status the delivery job acts on — a reminder created with any other status is stored but never fires. Each created reminder is assigned to the user who owns the API key, since notifications are sent per assignee. That assignment enables email by default; use `notifyEmail`/`notifyText` to change it.\n\nSetting `status` to `Completed` on a reminder that has a `recurringCadence` generates the next occurrence in the series, matching what completing it in the app does.","properties":{"name":{"type":"string","maxLength":255},"dataPointId":{"type":"integer","minimum":1},"dataPointType":{"type":"string","enum":["Property","Contact"]},"status":{"type":"string","maxLength":25},"setDate":{"type":"string","description":"When the reminder fires. Required on create. Date-only YYYY-MM-DD is stored as noon UTC."},"endDate":{"type":"string","maxLength":10},"recurringCadence":{"type":"string","maxLength":25},"alert":{"type":"string","maxLength":125},"notifyEmail":{"type":"boolean","default":true,"description":"Email the API key's owner when the reminder fires. On by default — a reminder with no enabled channel is stored and picked up by the delivery job, but notifies nobody."},"notifyText":{"type":"boolean","default":false,"description":"SMS the API key's owner. Off by default, and silently skipped if that user has no phone number on file."}}},"NoteCreate":{"type":"object","required":["note"],"description":"`createdDate` and `authorUserId` let a note carried over from another system keep its original date and author. Both default to now and the API key's owner; `createdDate` cannot be in the future, and `authorUserId` must be a member of the dataset.","properties":{"note":{"type":"string","maxLength":10000},"createdDate":{"type":"string","format":"date-time"},"authorUserId":{"type":"integer","minimum":1}}},"NotePatch":{"type":"object","description":"At least one field is required.","properties":{"note":{"type":"string","maxLength":10000},"isPinned":{"type":"boolean"},"createdDate":{"type":"string","format":"date-time","description":"Optional backdating; cannot be in the future."}}},"QueryFilter":{"type":"object","required":["field","op","value"],"properties":{"field":{"type":"string","description":"Filterable field name on the target resource (mapped columns plus `tags`; see the per-resource `Filterable fields` list in the /query description). `tags` supports only `contains` and `in`."},"op":{"type":"string","enum":["eq","neq","in","contains","gt","gte","lt","lte"]},"value":{"description":"Scalar for most operators; an array for `in`; a substring for `contains`."}}},"QuerySort":{"type":"object","required":["field","direction"],"properties":{"field":{"type":"string","description":"Sortable field name on the target resource (mapped columns only; see the per-resource `Sortable fields` list in the /query description)."},"direction":{"type":"string","enum":["asc","desc"]}}},"QueryRequest":{"type":"object","required":["resource"],"properties":{"resource":{"type":"string","enum":["properties","contacts","reminders"]},"filters":{"type":"array","items":{"$ref":"#/components/schemas/QueryFilter"}},"fields":{"type":"array","items":{"type":"string"},"description":"Subset of queryable fields to return; omitted = the resource's default set."},"sort":{"type":"array","items":{"$ref":"#/components/schemas/QuerySort"}},"limit":{"type":"integer","minimum":1,"maximum":100,"default":25},"offset":{"type":"integer","minimum":0,"default":0}}},"QueryResponse":{"type":"object","required":["data","total","limit","offset"],"properties":{"data":{"type":"array","items":{"type":"object"}},"total":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}},"QueryFields_properties":{"type":"string","enum":["id","name","address","units","lotSize","ownerName","email","phoneNumber","status","priority","category","addressCity","addressState","postalCode","county","squareFeet","propertyType","propertySubtype","yearBuilt","apn","lastSalePrice","lastSaleDate","source","createdAt","tags","customFields"],"description":"Valid `fields[]` (selection) values for resource \"properties\"."},"QueryFilterFields_properties":{"type":"string","enum":["id","name","address","units","lotSize","ownerName","email","phoneNumber","status","priority","category","addressCity","addressState","postalCode","county","squareFeet","propertyType","propertySubtype","yearBuilt","apn","lastSalePrice","lastSaleDate","source","createdAt","tags"],"description":"Valid `filters[].field` values for resource \"properties\"."},"QuerySortFields_properties":{"type":"string","enum":["id","name","address","units","lotSize","ownerName","email","phoneNumber","status","priority","category","addressCity","addressState","postalCode","county","squareFeet","propertyType","propertySubtype","yearBuilt","apn","lastSalePrice","lastSaleDate","source","createdAt"],"description":"Valid `sort[].field` values for resource \"properties\"."},"QueryFields_contacts":{"type":"string","enum":["id","firstName","lastName","email","type","phone","companyName","title","priority","address","city","state","postalCode","county","createdAt","tags","emails","phones","customFields"],"description":"Valid `fields[]` (selection) values for resource \"contacts\"."},"QueryFilterFields_contacts":{"type":"string","enum":["id","firstName","lastName","email","type","phone","companyName","title","priority","address","city","state","postalCode","county","createdAt","tags"],"description":"Valid `filters[].field` values for resource \"contacts\"."},"QuerySortFields_contacts":{"type":"string","enum":["id","firstName","lastName","email","type","phone","companyName","title","priority","address","city","state","postalCode","county","createdAt"],"description":"Valid `sort[].field` values for resource \"contacts\"."},"QueryFields_reminders":{"type":"string","enum":["id","name","dataPointId","dataPointType","status","setDate","endDate","recurringCadence","alert","createdAt"],"description":"Valid `fields[]` (selection) values for resource \"reminders\"."},"QueryFilterFields_reminders":{"type":"string","enum":["id","name","dataPointId","dataPointType","status","setDate","endDate","recurringCadence","alert","createdAt"],"description":"Valid `filters[].field` values for resource \"reminders\"."},"QuerySortFields_reminders":{"type":"string","enum":["id","name","dataPointId","dataPointType","status","setDate","endDate","recurringCadence","alert","createdAt"],"description":"Valid `sort[].field` values for resource \"reminders\"."}}},"security":[{"apiKey":[]}],"tags":[{"name":"Datasets","description":"Datasets the API key can reach. Start here: every other endpoint needs a `datasetId`."},{"name":"Properties","description":"Deals and leads. Stored in the `leads` table (legacy naming)."},{"name":"Property Notes","description":"Notes attached to a property."},{"name":"Contacts","description":"People and companies."},{"name":"Contact Notes","description":"Notes attached to a contact."},{"name":"Contact Activities","description":"Logged calls, emails, meetings, and texts with a contact."},{"name":"Property Contacts","description":"Associations between a property and the contacts involved in it."},{"name":"Reminders","description":"Tasks and follow-ups. Completing a recurring reminder generates the next occurrence."},{"name":"Query","description":"Structured filtering across resources when the list endpoints' query parameters aren't enough."},{"name":"Meta","description":"Machine-readable description of this API."}],"paths":{"/openapi.json":{"get":{"security":[],"tags":["Meta"],"summary":"Get the external API OpenAPI document","responses":{"200":{"description":"OpenAPI document"}}}},"/datasets":{"get":{"tags":["Datasets"],"summary":"List datasets accessible to the API key","responses":{"200":{"description":"Dataset list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Dataset"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}}},"/datasets/{datasetId}/properties":{"get":{"tags":["Properties"],"summary":"List properties","description":"Lists leads and deals together. Pass `category=Lead` or `category=Deal` to work with one side on its own; both sides support the same `sort`/`order` and `status` parameters.\n\nFor anything beyond these parameters — multiple filters at once, ranges, partial matches — use `POST /query` with `resource: \"properties\"`.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"sort","in":"query","required":false,"description":"Field to sort by. Unknown fields are rejected with a 400. Results are always tie-broken by `id` descending so paging stays stable.","schema":{"type":"string","enum":["id","name","address","units","lotSize","ownerName","email","phoneNumber","status","priority","category","addressCity","addressState","postalCode","county","squareFeet","propertyType","propertySubtype","yearBuilt","apn","lastSalePrice","lastSaleDate","source","createdAt"],"default":"id"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"status","in":"query","required":false,"description":"Exact match on the deal status.","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"description":"Restrict to leads or to deals. Omit to return both.","schema":{"type":"string","enum":["Lead","Deal"]}}],"responses":{"200":{"description":"Property list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Property"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}},"post":{"tags":["Properties"],"summary":"Create a property","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyWrite"}}}},"responses":{"201":{"description":"Created property","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Property"}}}}}}},"/datasets/{datasetId}/properties/search":{"post":{"tags":["Search"],"summary":"Search for possible duplicate properties","description":"Read-only; a `read` key is sufficient. Returns zero or more possible matches for the caller to judge — it never decides on the caller's behalf, and never creates or updates anything.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertySearch"}}}},"responses":{"200":{"description":"Possible matches","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertySearchResponse"}}}},"400":{"description":"No usable criteria supplied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/datasets/{datasetId}/properties/{id}":{"get":{"tags":["Properties"],"summary":"Get a property","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Property","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Property"}}}}}},"patch":{"tags":["Properties"],"summary":"Update a property","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyWrite"}}}},"responses":{"200":{"description":"Updated property","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Property"}}}}}}},"/datasets/{datasetId}/properties/{id}/notes":{"get":{"tags":["Property Notes"],"summary":"List notes for a property","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Property note list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PropertyNote"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}},"post":{"tags":["Property Notes"],"summary":"Create a property note","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteCreate"}}}},"responses":{"201":{"description":"Created note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyNote"}}}}}}},"/datasets/{datasetId}/properties/{id}/notes/{noteId}":{"get":{"tags":["Property Notes"],"summary":"Get a property note","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"noteId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Property note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyNote"}}}}}},"patch":{"tags":["Property Notes"],"summary":"Update a property note","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"noteId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotePatch"}}}},"responses":{"200":{"description":"Updated note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyNote"}}}}}},"delete":{"tags":["Property Notes"],"summary":"Archive a property note (soft delete)","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"noteId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"204":{"description":"Archived"}}}},"/datasets/{datasetId}/contacts":{"get":{"tags":["Contacts"],"summary":"List contacts","description":"Each contact carries its full `emails` and `phones` lists, so a caller building a call list from a page does not need a follow-up request per contact.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"sort","in":"query","required":false,"description":"Field to sort by. Unknown fields are rejected with a 400. Results are always tie-broken by `id` descending so paging stays stable.","schema":{"type":"string","enum":["id","firstName","lastName","email","type","phone","companyName","title","priority","address","city","state","postalCode","county","createdAt"],"default":"id"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"Contact list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactDetail"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}},"post":{"tags":["Contacts"],"summary":"Create a contact","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactWrite"}}}},"responses":{"201":{"description":"Created contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactDetail"}}}}}}},"/datasets/{datasetId}/contacts/search":{"post":{"tags":["Search"],"summary":"Search for possible duplicate contacts","description":"Read-only; a `read` key is sufficient. Returns zero or more possible matches for the caller to judge — it never decides on the caller's behalf, and never creates or updates anything.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactSearch"}}}},"responses":{"200":{"description":"Possible matches","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactSearchResponse"}}}},"400":{"description":"No usable criteria supplied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/datasets/{datasetId}/contacts/{id}":{"get":{"tags":["Contacts"],"summary":"Get a contact","description":"Returns the contact plus its full multi-value `emails` and `phones` lists. The list endpoints return neither — fetch the record here when you need every number.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Contact with all emails and phones","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactDetail"}}}}}},"patch":{"tags":["Contacts"],"summary":"Update a contact","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactWrite"}}}},"responses":{"200":{"description":"Updated contact","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactDetail"}}}}}}},"/datasets/{datasetId}/contacts/{id}/notes":{"get":{"tags":["Contact Notes"],"summary":"List notes for a contact","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Contact note list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactNote"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}},"post":{"tags":["Contact Notes"],"summary":"Create a contact note","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NoteCreate"}}}},"responses":{"201":{"description":"Created note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactNote"}}}}}}},"/datasets/{datasetId}/contacts/{id}/notes/{noteId}":{"get":{"tags":["Contact Notes"],"summary":"Get a contact note","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"noteId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Contact note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactNote"}}}}}},"patch":{"tags":["Contact Notes"],"summary":"Update a contact note","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"noteId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotePatch"}}}},"responses":{"200":{"description":"Updated note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactNote"}}}}}},"delete":{"tags":["Contact Notes"],"summary":"Archive a contact note (soft delete)","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"noteId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"204":{"description":"Archived"}}}},"/datasets/{datasetId}/contacts/{id}/activities":{"get":{"tags":["Contact Activities"],"summary":"List logged activities for a contact","description":"Most recent interaction first.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Activity list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Activity"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}},"post":{"tags":["Contact Activities"],"summary":"Log an activity against a contact","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityCreate"}}}},"responses":{"201":{"description":"Created activity","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Activity"}}}},"409":{"description":"An activity with this source and externalId already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/datasets/{datasetId}/property-contacts":{"get":{"tags":["Property Contacts"],"summary":"List property-contact links","description":"Pass exactly one of `propertyId` (the contacts on a property) or `contactId` (the properties a contact is on).","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"propertyId","in":"query","required":false,"schema":{"type":"integer","minimum":1}},{"name":"contactId","in":"query","required":false,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Link list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PropertyContactLink"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}},"400":{"description":"Neither or both of propertyId and contactId were supplied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Property Contacts"],"summary":"Link a property to a contact","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyContactLinkCreate"}}}},"responses":{"201":{"description":"Created link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PropertyContactLink"}}}},"409":{"description":"This property and contact are already linked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/datasets/{datasetId}/reminders":{"get":{"tags":["Reminders"],"summary":"List reminders","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"sort","in":"query","required":false,"description":"Field to sort by. Unknown fields are rejected with a 400. Results are always tie-broken by `id` descending so paging stays stable.","schema":{"type":"string","enum":["id","name","dataPointId","dataPointType","status","setDate","endDate","recurringCadence","alert","createdAt"],"default":"id"}},{"name":"order","in":"query","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"Reminder list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Reminder"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}},"post":{"tags":["Reminders"],"summary":"Create a reminder","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReminderWrite"}}}},"responses":{"201":{"description":"Created reminder","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reminder"}}}}}}},"/datasets/{datasetId}/reminders/{id}":{"get":{"tags":["Reminders"],"summary":"Get a reminder","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Reminder","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reminder"}}}}}},"patch":{"tags":["Reminders"],"summary":"Update a reminder","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReminderWrite"}}}},"responses":{"200":{"description":"Updated reminder","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Reminder"}}}}}}},"/datasets/{datasetId}/customizations/deal-statuses":{"get":{"tags":["Taxonomy"],"summary":"List the dataset's deal statuses","description":"The pipeline stages, in order, plus Lead when it is not stored as a row. A property's `status` should be one of these values — anything else is stored but cannot be mapped by the in-app pipeline reports.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Deal status list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DealStatusOption"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}}},"/datasets/{datasetId}/customizations/deal-statuses/{id}/deals":{"get":{"tags":["Taxonomy"],"summary":"List active deals on a deal status","description":"`id` is the deal-status vocabulary id from GET /customizations/deal-statuses (numeric, or `default:lead`). Unknown ids are 404. Archived deals are omitted. Prefer POST /query when you need full property records.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Numeric id from the vocabulary list, or a default:<slug> id."}}],"responses":{"200":{"description":"Deals on this status","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DealMembership"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}},"404":{"description":"Deal status not found in this dataset","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/datasets/{datasetId}/customizations/tags":{"get":{"tags":["Taxonomy"],"summary":"List the dataset's tags","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Tag list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/NamedOption"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}}},"/datasets/{datasetId}/customizations/property-types":{"get":{"tags":["Taxonomy"],"summary":"List the dataset's property types","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Property type list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TypeOption"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}}},"/datasets/{datasetId}/customizations/contact-types":{"get":{"tags":["Taxonomy"],"summary":"List the dataset's contact types","description":"The role values a contact's `type` accepts: exactly the types this dataset has.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"Contact type list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TypeOption"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}}}}},"/datasets/{datasetId}/customizations/contact-types/{id}/contacts":{"get":{"tags":["Taxonomy"],"summary":"List active contacts of a contact type","description":"`id` is the contact-type vocabulary id from GET /customizations/contact-types (numeric; a legacy `default:<slug>` id such as `default:broker` resolves to the dataset's type with that name and is 404 once it is deleted or renamed). Contacts store the type's current name, not this id. Unknown ids are 404. Two vocabulary rows with the same name are 409. Archived contacts are omitted. Prefer POST /query when you need full contact records.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"id","in":"path","required":true,"schema":{"type":"string","description":"Numeric id from the vocabulary list, or a default:<slug> id."}}],"responses":{"200":{"description":"Contacts of this type","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ContactMembership"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}},"404":{"description":"Contact type not found in this dataset","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Another contact type in this dataset uses the same name","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/datasets/{datasetId}/custom-fields":{"get":{"tags":["Taxonomy"],"summary":"List custom field definitions","description":"The customer-defined columns on properties and contacts. Omit `entityType` to get both.","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"entityType","in":"query","required":false,"schema":{"type":"string","enum":["Property","Contact"]}}],"responses":{"200":{"description":"Custom field definition list","content":{"application/json":{"schema":{"type":"object","required":["data","total"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CustomFieldDefinition"}},"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer","maximum":100}}}}}},"400":{"description":"Invalid entityType","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/datasets/{datasetId}/query":{"post":{"tags":["Query"],"summary":"Query a resource with structured filters","description":"Filter, sort, and field-select on a single resource type. Set `resource` to one of `properties`, `contacts`, or `reminders`.\n\n`fields[]`, `filters[].field`, and `sort[].field` accept different sets of **public field names** — the sets are listed per resource below. `fields[]` is the widest (it includes virtual fields such as `tags`, `emails`, `phones`, and `customFields`); `filters[].field` adds `tags` to the mapped columns; `sort[].field` accepts mapped columns only. Unknown fields or operators return `400`.\n\nThe `tags` filter supports only the `contains` and `in` operators.\n\nOperators: `eq`, `neq`, `in`, `contains`, `gt`, `gte`, `lt`, `lte`.\n\n### properties\nSelectable fields (`fields[]`): `id`, `name`, `address`, `units`, `lotSize`, `ownerName`, `email`, `phoneNumber`, `status`, `priority`, `category`, `addressCity`, `addressState`, `postalCode`, `county`, `squareFeet`, `propertyType`, `propertySubtype`, `yearBuilt`, `apn`, `lastSalePrice`, `lastSaleDate`, `source`, `createdAt`, `tags`, `customFields`\n\nFilterable fields (`filters[].field`): `id`, `name`, `address`, `units`, `lotSize`, `ownerName`, `email`, `phoneNumber`, `status`, `priority`, `category`, `addressCity`, `addressState`, `postalCode`, `county`, `squareFeet`, `propertyType`, `propertySubtype`, `yearBuilt`, `apn`, `lastSalePrice`, `lastSaleDate`, `source`, `createdAt`, `tags`\n\nSortable fields (`sort[].field`): `id`, `name`, `address`, `units`, `lotSize`, `ownerName`, `email`, `phoneNumber`, `status`, `priority`, `category`, `addressCity`, `addressState`, `postalCode`, `county`, `squareFeet`, `propertyType`, `propertySubtype`, `yearBuilt`, `apn`, `lastSalePrice`, `lastSaleDate`, `source`, `createdAt`\n\nDefault fields (when `fields` omitted): `id`, `name`, `address`, `status`, `category`, `priority`, `ownerName`, `email`, `phoneNumber`, `addressCity`, `addressState`, `postalCode`, `createdAt`, `tags`, `customFields`\n\n### contacts\nSelectable fields (`fields[]`): `id`, `firstName`, `lastName`, `email`, `type`, `phone`, `companyName`, `title`, `priority`, `address`, `city`, `state`, `postalCode`, `county`, `createdAt`, `tags`, `emails`, `phones`, `customFields`\n\nFilterable fields (`filters[].field`): `id`, `firstName`, `lastName`, `email`, `type`, `phone`, `companyName`, `title`, `priority`, `address`, `city`, `state`, `postalCode`, `county`, `createdAt`, `tags`\n\nSortable fields (`sort[].field`): `id`, `firstName`, `lastName`, `email`, `type`, `phone`, `companyName`, `title`, `priority`, `address`, `city`, `state`, `postalCode`, `county`, `createdAt`\n\nDefault fields (when `fields` omitted): `id`, `firstName`, `lastName`, `email`, `type`, `phone`, `companyName`, `title`, `priority`, `city`, `state`, `createdAt`, `tags`, `emails`, `phones`, `customFields`\n\n### reminders\nSelectable fields (`fields[]`): `id`, `name`, `dataPointId`, `dataPointType`, `status`, `setDate`, `endDate`, `recurringCadence`, `alert`, `createdAt`\n\nFilterable fields (`filters[].field`): `id`, `name`, `dataPointId`, `dataPointType`, `status`, `setDate`, `endDate`, `recurringCadence`, `alert`, `createdAt`\n\nSortable fields (`sort[].field`): `id`, `name`, `dataPointId`, `dataPointType`, `status`, `setDate`, `endDate`, `recurringCadence`, `alert`, `createdAt`\n\nDefault fields (when `fields` omitted): `id`, `name`, `dataPointId`, `dataPointType`, `status`, `setDate`, `createdAt`","parameters":[{"name":"datasetId","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryRequest"}}}},"responses":{"200":{"description":"Query results","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QueryResponse"}}}},"400":{"description":"Invalid resource, field, or operator","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}