openapi: 3.0.3 info: title: Aptly App Contacts API version: '1.0' description: 'The Aptly API lets you read and write cards on any Aptly board from external systems. All requests require an API key passed as the `x-token` header. API keys are scoped to your company and work across all boards. ' servers: - url: https://core-api.getaptly.com description: Production security: - ApiKeyHeader: [] tags: - name: Contacts paths: /api/contacts: get: summary: List contacts description: 'Returns a paginated list of contacts scoped to your company. All filter params are optional and ANDed together. ' operationId: listContacts tags: - Contacts security: - ApiKeyHeader: [] - DelegateToken: [] parameters: - name: page in: query required: true schema: type: integer minimum: 0 description: Zero-based page index. - name: contact_type in: query schema: type: string description: Filter by contact type name. - name: email in: query schema: type: string description: Filter by exact email address (case-insensitive). - name: phone in: query schema: type: string description: Filter by phone number (digits only, partial match). - name: name in: query schema: type: string description: Filter by full name (case-insensitive, all words must match). - name: updated_after in: query schema: type: string format: date-time description: Return only contacts updated after this ISO 8601 timestamp. - name: updated_before in: query schema: type: string format: date-time description: Return only contacts updated before this ISO 8601 timestamp. responses: '200': description: Paginated contact list. headers: x-offset: schema: type: integer description: Current page index. x-count: schema: type: integer description: Total matching contacts. x-size: schema: type: integer description: Page size. content: application/json: schema: type: object properties: data: type: array items: type: object count: type: integer page: type: integer pageSize: type: integer '400': description: Missing or invalid parameters. '401': description: Invalid or missing API key. post: summary: Create or update a contact description: 'Creates a new contact or updates an existing one (upsert). **Lookup order:** 1. If `_id` is provided, finds by ID. 2. Otherwise finds by first email address. 3. If no match is found, creates a new contact. **Body formats** — either native or legacy (capitalized keys) are accepted: *Native:* `firstname`, `lastname`, `email` (string or array), `phone` (array of `{number, type}`), `typeId`, `contactType`, `isCompany`, `title`, `company`, `imageUrl`, `customFields` *Legacy:* `"First Name"`, `"Last Name"`, `Email`, `"Mobile Phone"`, `"Work Phone"`, `"Home Phone"`, `"Contact Type"`, `Title`, `Company` Returns the enriched contact with custom fields populated by their type definitions. ' operationId: upsertContact tags: - Contacts security: - ApiKeyHeader: [] - DelegateToken: [] requestBody: required: true content: application/json: schema: type: object properties: _id: type: string description: Existing contact ID — when provided, updates that contact. firstname: type: string lastname: type: string email: oneOf: - type: string - type: array items: type: string description: One or more email addresses. phone: type: array items: type: object properties: number: type: string type: type: string enum: - mobile - work - home typeId: type: string description: Contact type ID. contactType: type: string description: Contact type name (alternative to `typeId` — resolved to an ID automatically). isCompany: type: boolean title: type: string company: type: string imageUrl: type: string description: Absolute URL to a JPG or PNG photo. customFields: type: object description: Map of custom field ID to value. Unknown field IDs are rejected. example: firstname: Jane lastname: Smith email: jane@example.com phone: - number: '+15555550100' type: mobile contactType: Tenant responses: '200': description: Contact created or updated. content: application/json: schema: $ref: '#/components/schemas/Contact' '400': description: Invalid input (bad URL, unknown custom field, invalid date, etc.). content: application/json: schema: $ref: '#/components/schemas/Error' '401': description: Invalid or missing API key. /api/contacts/by-email: post: summary: Look up contacts by email description: 'Returns contacts whose email address matches one or more of the provided values. Matching is case-insensitive and exact. Results are scoped to your company. ' operationId: getContactsByEmail tags: - Contacts security: - ApiKeyHeader: [] - DelegateToken: [] requestBody: required: true content: application/json: schema: type: object required: - email properties: email: oneOf: - type: string - type: array items: type: string description: A single email address or an array of email addresses to look up. limit: type: integer default: 20 description: Maximum number of results to return. skip: type: integer default: 0 description: Number of results to skip (for pagination). example: email: jane@example.com limit: 20 skip: 0 responses: '200': description: Matching contacts. content: application/json: schema: type: object properties: contacts: type: array items: $ref: '#/components/schemas/Contact' '400': description: Missing or invalid email field. content: application/json: schema: $ref: '#/components/schemas/Error' '401': description: Invalid or missing API key. /api/contacts/verify-email: post: summary: Initiate contact email verification description: 'Looks up an email address against your org''s contact database. If a match is found, generates a cryptographically strong 6-digit code, sends it to the address, and returns a `requestId` and `verifyUrl` to complete the verification. The code expires after 10 minutes and can only be used once. ' operationId: initiateContactVerification tags: - Contacts security: - ApiKeyHeader: [] - DelegateToken: [] requestBody: required: true content: application/json: schema: type: object required: - email properties: email: type: string description: The email address to verify. emailSubject: type: string description: Subject line for the verification email. Defaults to "Your verification code". replyTo: type: string description: Reply-To address for the verification email. emailHtml: type: string description: 'Custom HTML body for the verification email. Supports two placeholders: `{{ verificationCode }}` — replaced with the 6-digit code. `{{ expirationTime }}` — replaced with the expiry duration (e.g. "10 minutes"). ' example: email: jane@example.com emailSubject: Your access code replyTo: support@example.com emailHtml:
Your code is {{ verificationCode }}. It expires in {{ expirationTime }}.
responses: '200': description: Verification initiated — code sent to the email address. content: application/json: schema: type: object properties: requestId: type: string description: Opaque ID used to complete the verification. verifyUrl: type: string description: API path to POST the code to (relative URL). example: requestId: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4 verifyUrl: /api/contacts/verify-email/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4/confirm '400': description: Missing or invalid email field. content: application/json: schema: $ref: '#/components/schemas/Error' '401': description: Invalid or missing API key. content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: No contact found with that email address. content: application/json: schema: $ref: '#/components/schemas/Error' '500': description: Verification request was created but the email could not be delivered (EMAIL_SEND_FAILURE). content: application/json: schema: $ref: '#/components/schemas/Error' /api/contacts/verify-email/{requestId}/confirm: post: summary: Confirm contact email verification description: 'Submits the 6-digit code received by email. Returns the matching contact records if the code is valid, not expired, and has not already been used. After 5 consecutive failed attempts the verification is permanently invalidated. The caller must re-initiate a new verification to try again. ' operationId: confirmContactVerification tags: - Contacts security: - ApiKeyHeader: [] - DelegateToken: [] parameters: - name: requestId in: path required: true schema: type: string description: The `requestId` returned by the initiate endpoint. requestBody: required: true content: application/json: schema: type: object required: - code properties: code: type: string description: The 6-digit verification code sent to the email address. example: code: 042815 responses: '200': description: Code accepted — returns verified status and matching contacts. content: application/json: schema: type: object properties: verified: type: boolean example: true contacts: type: array items: $ref: '#/components/schemas/Contact' '400': description: Missing code field. content: application/json: schema: $ref: '#/components/schemas/Error' '401': description: Invalid or missing API key, or code is invalid/expired/already used. content: application/json: schema: $ref: '#/components/schemas/Error' /api/contacts/{contactId}: get: summary: Get a contact description: Returns a single contact by its ID, with custom fields enriched by their type definitions. operationId: getContact tags: - Contacts security: - ApiKeyHeader: [] - DelegateToken: [] parameters: - name: contactId in: path required: true schema: type: string description: The contact's `_id`. responses: '200': description: Contact record. content: application/json: schema: type: object properties: data: type: array items: $ref: '#/components/schemas/Contact' count: type: integer page: type: integer pageSize: type: integer '401': description: Invalid or missing API key. post: summary: Update a contact description: 'Updates an existing contact by ID using the same upsert logic as `POST /api/contacts`. The `_id` is taken from the URL — any `_id` in the body is ignored. Accepts the same **native** or **legacy** body formats as `POST /api/contacts`. Returns the updated, enriched contact. ' operationId: updateContact tags: - Contacts security: - ApiKeyHeader: [] - DelegateToken: [] parameters: - name: contactId in: path required: true schema: type: string description: The contact's `_id`. requestBody: required: true content: application/json: schema: type: object properties: firstname: type: string lastname: type: string email: oneOf: - type: string - type: array items: type: string description: One or more email addresses. phone: type: array items: type: object properties: number: type: string type: type: string enum: - mobile - work - home typeId: type: string description: Contact type ID. contactType: type: string description: Contact type name (alternative to `typeId`). isCompany: type: boolean title: type: string company: type: string imageUrl: type: string description: Absolute URL to a JPG or PNG photo. customFields: type: object description: Map of custom field ID to value. example: firstname: Jane lastname: Smith email: jane@example.com responses: '200': description: Updated contact. content: application/json: schema: $ref: '#/components/schemas/Contact' '400': description: Invalid input. content: application/json: schema: $ref: '#/components/schemas/Error' '401': description: Invalid or missing API key. components: schemas: Contact: type: object properties: _id: type: string description: MongoDB ObjectId of the contact. uuid: type: string firstname: type: string lastname: type: string fullName: type: string description: Computed display name (first + last, or company name for org records). duogram: type: string description: Two-letter initials derived from first and last name. photoId: type: string nullable: true imageUrl: type: string nullable: true description: CDN thumbnail URL for the contact's photo, or null if none. email: type: string phone: type: string typeId: type: string companyId: type: string address: type: object title: type: string alerts: type: array items: type: object company: type: string description: Company name — populated when isCompany is true. isCompany: type: boolean Error: type: object properties: error: type: string message: type: string securitySchemes: ApiKeyHeader: type: apiKey in: header name: x-token DelegateToken: type: apiKey in: header name: Authorization description: 'Delegate token issued by the platform. Format: `DelegateToken