Documentation
LinkyCal homeCreate a LinkyCal account
Developer Documentation

LinkyCal API Documentation

Everything you need to integrate forms, booking, and contact management into your product.

OpenAPI specView llms.txt

Installation

The fastest way to add LinkyCal to your site is with the embeddable widgets. Drop in a script tag and initialize with your project slug.

Booking Widgethtml
<!-- Booking Widget -->
<script src="https://cdn.linkycal.com/widgets/booking.js"></script>
<script>
  LinkyCal.booking({
    projectSlug: "your-project",
    container: "#booking-widget"
  });
</script>
Form Widgethtml
<!-- Form Widget -->
<script src="https://cdn.linkycal.com/widgets/form.js"></script>
<script>
  LinkyCal.form({
    projectSlug: "your-project",
    formSlug: "contact",
    container: "#form-widget"
  });
</script>

Quick Start

Get up and running in four steps:

  1. Create a project in the dashboard
  2. Create an event type or form
  3. Use the anonymous visitor endpoints in your public form or booking UI
  4. For server-side management, create a project API key under MCP & APIs
Check available slotsbash
# Check available slots
curl "https://linkycal.com/api/v1/availability/your-project?date=2026-08-12&timezone=UTC&eventTypeSlug=consultation"

How It Works

Every interaction with LinkyCal follows a straightforward request/response flow:

  1. Client sends request to LinkyCal API
  2. Server validates input and checks availability/form config
  3. Action is performed (booking created, form step submitted, etc.)
  4. Response returned with result
  5. Optional: workflow triggers fire (email, webhook, tag)
JSON errors include a descriptive error field and may include a stable code. Successful responses vary by operation: JSON resources, file bodies, and empty 204 responses are all used where appropriate.

API Families

Visitor API · /api/v1/*

Canonical anonymous endpoints for availability, bookings, and multi-step form submissions. These routes are safe for visitor-side code and are rate limited by IP.

Share-link API · /api/public/*

Public link resolution and form configuration used by LinkyCal share pages. The response-creation and step-submission variants are retained for compatibility; new direct integrations should prefer /api/v1/*.

Management API · /api/projects/:projectId/*

Server-side project administration with a project-scoped API key. Never expose this credential in a browser, widget, or public form.

Endpoint Catalog

Anonymous visitor endpoints cover form responses, file uploads, availability, booking creation, widgets, and public link resolution. They are rate limited and need no credential. Never put an API key in visitor-side code.

The protected management API uses the canonical /api/projects/:projectId/* routes. Send Authorization: Bearer lc_live_... from a server, secure automation, or local agent. The key must belong to the project ID in the URL and the project must have API access on its current plan.

The complete machine-readable operation list and security declaration are in the OpenAPI 3.1 specification. Agent-oriented integration guidance is available in llms.txt.

Dashboard-only Operations

Project creation and deletion, members, API-key management, billing, and OAuth connections are dashboard-only. Account, team, and onboarding operations are also session-only. API keys receive api_key_route_forbidden for these operations; use the dashboard instead.

Create Response

POST /api/v1/forms/:projectSlug/:formSlug/responses

Start a new form response. Returns the response object and full form config including all steps and fields.

PropertyTypeRequiredDescription
projectSlugstringYesYour project's URL slug (path param)
formSlugstringYesThe form's URL slug (path param)
Requestbash
curl -X POST "https://linkycal.com/api/v1/forms/acme/contact/responses" \
  -H "Content-Type: application/json"
Responsejson
{
  "response": {
    "id": "resp_a1b2c3d4",
    "formId": "form_x1y2z3",
    "currentStepIndex": 0,
    "status": "in_progress"
  },
  "form": {
    "id": "form_x1y2z3",
    "name": "Contact Form",
    "steps": [
      {
        "id": "step_1",
        "title": "Your Details",
        "fields": [
          { "id": "fld_1", "type": "text", "label": "Full Name", "required": true },
          { "id": "fld_2", "type": "email", "label": "Email", "required": true }
        ]
      }
    ]
  }
}

Submit Step

PATCH /api/v1/forms/:projectSlug/:formSlug/responses/:responseId/steps/:stepIndex

Submit field values for a specific step. Steps must be submitted in order (0, 1, 2...). Pass complete: true on the final visible step to mark the response completed.

PropertyTypeRequiredDescription
fieldsarrayYesArray of { fieldId, value } objects. File fields may include fileUrl from the upload endpoint.
completebooleanNoSet true on the last visible step to finalize the response. Required for forms with conditional steps, where the server can't infer the final step from the index alone.
Requestbash
curl -X PATCH "https://linkycal.com/api/v1/forms/acme/contact/responses/resp_a1b2c3d4/steps/0" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": [
      { "fieldId": "fld_1", "value": "Jane Smith" },
      { "fieldId": "fld_2", "value": "jane@example.com" }
    ]
  }'
Responsejson
{
  "response": {
    "id": "resp_a1b2c3d4",
    "status": "in_progress",
    "currentStepIndex": 1,
    "completedAt": null
  }
}
When the response status changes to completed, all steps have been submitted.

Upload File

POST /api/v1/forms/:projectSlug/:formSlug/responses/:responseId/uploads

Upload a file for a file field before submitting that step through the JSON API. The upload response returns a private file pointer that you include as fileUrl.

Upload requestbash
curl -X POST "https://linkycal.com/api/v1/forms/acme/contact/responses/resp_a1b2c3d4/uploads" \
  -F "fieldId=resume" \
  -F "file=@./resume.pdf"
Submit uploaded filebash
curl -X PATCH "https://linkycal.com/api/v1/forms/acme/contact/responses/resp_a1b2c3d4/steps/0" \
  -H "Content-Type: application/json" \
  -d '{
    "fields": [
      {
        "fieldId": "resume",
        "value": "resume.pdf",
        "fileUrl": "form-responses/project/form/response/resume/upload-id.pdf"
      }
    ]
  }'
Uploaded respondent files are private by default. Dashboard users open them from the response drawer. API consumers can download them with a project API key at GET /api/v1/forms/:projectSlug/:formSlug/responses/:responseId/files/:valueId.

Native HTML Form

POST /api/public/forms/:projectSlug/:formSlug/submit

Post a regular browser form directly to LinkyCal without any client-side JavaScript. LinkyCal returns a hosted thank-you page by default, or a redirect if you configure one in the form builder.

HTML Form Actionhtml
<form action="https://linkycal.com/api/public/forms/acme/contact/submit" method="post" enctype="multipart/form-data">
  <input type="text" name="full_name" required />
  <input type="email" name="email" required />
  <input type="file" name="resume" />
  <textarea name="message"></textarea>
  <button type="submit">Send</button>
</form>
Use your form field IDs as the HTML input name values. You can get the exact IDs from the form builder or the generated form API prompt. Include enctype="multipart/form-data" when the form has file inputs.

Get Form Config

GET /api/widget/form/:projectSlug/:formSlug/config

Returns the full form structure with steps, fields, and validation rules. This is the same endpoint used internally by the form widget.

Requestbash
curl "https://linkycal.com/api/widget/form/acme/contact/config"
Responsejson
{
  "form": {
    "id": "form_x1y2z3",
    "name": "Contact Form",
    "slug": "contact",
    "steps": [
      {
        "id": "step_1",
        "title": "Your Details",
        "fields": [
          { "id": "fld_1", "type": "text", "label": "Full Name", "required": true },
          { "id": "fld_2", "type": "email", "label": "Email", "required": true },
          { "id": "fld_3", "type": "phone", "label": "Phone", "required": false }
        ]
      }
    ]
  }
}

Other visitor endpoints

Supporting anonymous routes used by public links, widgets, analytics, and older form integrations.

GET
/api/v1/event-types/:projectSlug/:eventSlug

Get public event type configuration.

POST
/api/v1/t

Record an anonymous visitor analytics event.

GET
/api/public/resolve/:projectSlug/:slug

Resolve a share-link slug to a form or event type.

POST
/api/public/forms/:projectSlug/:formSlug/responses

Legacy form response creation.

PATCH
/api/public/forms/:projectSlug/:formSlug/responses/:responseId/steps/:stepIndex

Legacy form step submission.

POST
/api/public/forms/:projectSlug/:formSlug/submit

Legacy single-request form submission.

GET
/api/uploads/:key

Download a public upload by object key.

  • The /api/public form write routes are retained for compatibility. New direct integrations should use the equivalent /api/v1 form routes.
  • Public upload URLs contain unguessable object keys; do not treat them as authorization for sensitive private response files.

Check Availability

GET /api/v1/availability/:projectSlug

Returns available time slots for a given event type on a specific date. Use this to display available times to your users before creating a booking.

PropertyTypeRequiredDescription
datestringYesDate in YYYY-MM-DD format
timezonestringNoIANA timezone, defaults to UTC
eventTypeSlugstringYesThe event type's URL slug
Requestbash
curl "https://linkycal.com/api/v1/availability/acme?date=2026-08-12&timezone=UTC&eventTypeSlug=consultation"
Responsejson
{
  "slots": [
    { "start": "2026-03-24T09:00:00Z", "end": "2026-03-24T09:30:00Z" },
    { "start": "2026-03-24T10:00:00Z", "end": "2026-03-24T10:30:00Z" }
  ],
  "date": "2026-03-24",
  "timezone": "UTC"
}

Create Booking

POST /api/v1/bookings

Create a new booking for an available time slot. The system validates slot availability before confirming. A confirmation email is automatically sent to the guest.

PropertyTypeRequiredDescription
projectSlugstringYesYour project's URL slug
eventTypeSlugstringYesThe event type's URL slug
startTimestringYesISO 8601 datetime
namestringYesGuest name
emailstringYesGuest email
notesstringNoAdditional notes from the guest
timezonestringYesGuest timezone (IANA format, e.g. America/New_York)
formFieldsobjectNoValues for the event type's custom booking-form fields, keyed by field ID
Requestbash
curl -X POST "https://linkycal.com/api/v1/bookings" \
  -H "Content-Type: application/json" \
  -d '{
    "projectSlug": "acme",
    "eventTypeSlug": "consultation",
    "startTime": "2026-03-24T09:00:00Z",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "notes": "Looking forward to our meeting",
    "timezone": "America/New_York"
  }'
Responsejson
{
  "booking": {
    "id": "bk_abc123",
    "eventTypeId": "et_xyz789",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "startTime": "2026-03-24T09:00:00Z",
    "endTime": "2026-03-24T09:30:00Z",
    "timezone": "America/New_York",
    "status": "confirmed",
    "notes": "Looking forward to our meeting",
    "createdAt": "2026-03-23T20:00:00Z"
  }
}
A confirmation email is automatically sent to the guest. If Google Calendar is connected, an event is created.

Cancel Booking

PATCH /api/projects/:projectId/bookings/:bookingId/cancel

Cancel an existing booking. The booking status is set to cancelled. If connected to Google Calendar, the calendar event is also removed.

This protected project route accepts either a dashboard session or a project API key. Server-side integrations should use the Bearer header below.
Requestbash
curl -X PATCH "https://linkycal.com/api/projects/proj_123/bookings/bk_abc123/cancel" \
  -H "Authorization: Bearer lc_live_your_api_key"
Responsejson
{
  "booking": {
    "id": "bk_abc123",
    "status": "cancelled",
    "cancelledAt": "2026-03-24T08:00:00Z"
  }
}

List Contacts

GET /api/projects/:projectId/contacts

Retrieve the first page of contacts for a project, with optional search and tag filtering. The response includes the full filtered total; see Contact list filters for the complete filter and pagination reference.

PropertyTypeRequiredDescription
searchstringNoSearch by name or email
tagIdstringNoFilter by tag ID
limitintegerNoPage size, default 50 and maximum 100
offsetintegerNoZero-based result offset
Responsejson
{
  "contacts": [
    {
      "id": "ct_m1n2o3",
      "name": "Jane Smith",
      "email": "jane@example.com",
      "phone": "+1-555-0123",
      "tags": [
        { "id": "tag_lead", "name": "Lead", "color": "#1B4332" }
      ],
      "lastActivityAt": "2026-07-22T08:30:00.000Z",
      "createdAt": "2026-03-20T10:00:00.000Z"
    }
  ],
  "total": 1
}

Create Contact

POST /api/projects/:projectId/contacts

Create a new contact in your project. Contacts are also created automatically when a form is submitted or a booking is made.

PropertyTypeRequiredDescription
namestringYesContact's full name
emailstringNoContact's email address
phonestringNoContact's phone number
notesstringNoInternal notes about the contact
Requestbash
curl -X POST "https://linkycal.com/api/projects/proj_123/contacts" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer lc_live_your_api_key" \
  -d '{
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+1-555-0123",
    "notes": "Met at conference"
  }'
Responsejson
{
  "contact": {
    "id": "ct_p4q5r6",
    "name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+1-555-0123",
    "notes": "Met at conference",
    "createdAt": "2026-03-24T14:00:00Z"
  }
}

Update Contact

PUT /api/projects/:projectId/contacts/:contactId

Update an existing contact's information. Only provided fields are updated.

Requestbash
curl -X PUT "https://linkycal.com/api/projects/proj_123/contacts/ct_p4q5r6" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer lc_live_your_api_key" \
  -d '{
    "name": "Jane Smith-Doe",
    "notes": "Updated: now a paying customer"
  }'
Responsejson
{
  "contact": {
    "id": "ct_p4q5r6",
    "name": "Jane Smith-Doe",
    "email": "jane@example.com",
    "phone": "+1-555-0123",
    "notes": "Updated: now a paying customer",
    "updatedAt": "2026-03-24T15:00:00Z"
  }
}

Projects and entitlements

Read and update the project addressed by the API key, and inspect the plan limits that apply to it.

GET
/api/projects/:projectId

Get the current project.

PUT
/api/projects/:projectId

Update project settings and branding.

GET
/api/projects/:projectId/entitlements

Get the subscription, usage, and effective plan limits.

  • Project creation and deletion remain dashboard-only.
  • PUT accepts name, slug, timezone, and onboarded; slugs use lowercase letters, digits, and hyphens.
  • Entitlements are the source of truth for API access and resource limits; do not hard-code plan assumptions.

Event types

Manage bookable meeting definitions and choose which connected calendars each event type reads and writes.

GET
/api/projects/:projectId/event-types

List event types.

POST
/api/projects/:projectId/event-types

Create an event type.

GET
/api/projects/:projectId/event-types/:eventTypeId

Get an event type.

PUT
/api/projects/:projectId/event-types/:eventTypeId

Update an event type.

DELETE
/api/projects/:projectId/event-types/:eventTypeId

Delete an event type.

GET
/api/projects/:projectId/event-types/:eventTypeId/calendars

Get destination and availability calendar selections.

PUT
/api/projects/:projectId/event-types/:eventTypeId/calendars

Replace destination and availability calendar selections.

  • Event type writes are subject to the project plan limit.
  • Create with name, slug, duration, and optional description, location, color, buffers, booking caps, confirmation mode, bookingFormId, settings, or copyFromEventTypeId.
  • Calendar IDs must belong to a connection available to the project or its team.

Schedules and availability rules

Manage reusable schedules, their weekly working windows, and date-specific availability overrides.

GET
/api/projects/:projectId/schedules

List schedules.

POST
/api/projects/:projectId/schedules

Create a schedule.

PUT
/api/projects/:projectId/schedules/:scheduleId

Update a schedule.

DELETE
/api/projects/:projectId/schedules/:scheduleId

Delete a schedule.

GET
/api/projects/:projectId/schedules/:scheduleId/rules

List weekly availability rules.

PUT
/api/projects/:projectId/schedules/:scheduleId/rules

Replace weekly availability rules.

GET
/api/projects/:projectId/schedules/:scheduleId/overrides

List date overrides.

POST
/api/projects/:projectId/schedules/:scheduleId/overrides

Create a date override.

DELETE
/api/projects/:projectId/schedules/:scheduleId/overrides/:overrideId

Delete a date override.

  • Times in rules and overrides are local to the schedule timezone.
  • Create schedules with name, timezone, and isDefault. Rules contain dayOfWeek, startTime, and endTime; 24:00 is valid only as an end time.
  • Replacing rules uses the complete submitted rule set; read the current rules before editing.

Booking management

Read bookings created by visitor flows and perform organizer-side state transitions.

GET
/api/projects/:projectId/bookings

List project bookings.

GET
/api/projects/:projectId/bookings/:bookingId

Get a booking.

PATCH
/api/projects/:projectId/bookings/:bookingId/cancel

Cancel a booking and optionally record a reason.

PATCH
/api/projects/:projectId/bookings/:bookingId/confirm

Confirm a pending booking.

PATCH
/api/projects/:projectId/bookings/:bookingId/decline

Decline a pending booking.

GET
/api/projects/:projectId/bookings/:bookingId/form-response

Get the intake form response attached to a booking.

  • Confirmation and cancellation may update Google Calendar and send email.
  • Cancellation accepts an optional reason. Decline accepts an optional reason and notify flag; confirmation requires no request body.
  • The visitor booking creation endpoint is documented separately under Booking API.

Forms and responses

Manage forms, ordered steps and fields, submitted responses, and private response files.

GET
/api/projects/:projectId/forms

List forms.

POST
/api/projects/:projectId/forms

Create a form.

GET
/api/projects/:projectId/forms/:formId

Get a form with its structure.

PUT
/api/projects/:projectId/forms/:formId

Update a form.

DELETE
/api/projects/:projectId/forms/:formId

Delete a form.

GET
/api/projects/:projectId/forms/:formId/steps

List form steps.

POST
/api/projects/:projectId/forms/:formId/steps

Create a form step.

PUT
/api/projects/:projectId/forms/:formId/steps/:stepId

Update a form step.

DELETE
/api/projects/:projectId/forms/:formId/steps/:stepId

Delete a form step.

PUT
/api/projects/:projectId/forms/:formId/steps/reorder

Reorder form steps.

GET
/api/projects/:projectId/forms/:formId/fields

List form fields.

POST
/api/projects/:projectId/forms/:formId/fields

Create a form field.

PUT
/api/projects/:projectId/forms/:formId/fields/:fieldId

Update a form field.

DELETE
/api/projects/:projectId/forms/:formId/fields/:fieldId

Delete a form field.

PUT
/api/projects/:projectId/forms/:formId/fields/reorder

Reorder form fields.

GET
/api/projects/:projectId/forms/:formId/responses

List form responses.

GET
/api/projects/:projectId/forms/:formId/responses/:responseId

Get a form response.

GET
/api/projects/:projectId/forms/:formId/responses/:responseId/files/:valueId

Download a private response file.

GET
/api/v1/forms/:projectSlug/:formSlug/responses/:responseId/files/:valueId

Download a private response file through the project-scoped API-key route.

DELETE
/api/projects/:projectId/form-responses/:responseId

Delete a form response.

  • Reorder endpoints accept the complete ordered list of IDs.
  • Forms use name, slug, type, status, and settings. Steps contain display and visibility data; fields contain type, label, validation, options, contact mapping, and visibility.
  • Deleting a response removes its stored values and owned response files.

Contact management

Manage CRM records, saved list and Kanban views, activity, next actions, stages, imports, and enrichment.

GET
/api/projects/:projectId/contacts

List and filter contacts.

POST
/api/projects/:projectId/contacts

Create a contact.

POST
/api/projects/:projectId/contacts/import

Import mapped contact rows.

GET
/api/projects/:projectId/contacts/:contactId

Get a contact with tags.

PUT
/api/projects/:projectId/contacts/:contactId

Update a contact.

DELETE
/api/projects/:projectId/contacts/:contactId

Delete a contact.

GET
/api/projects/:projectId/contacts/:contactId/activities

List the cursor-paginated contact timeline and category counts.

PUT
/api/projects/:projectId/contacts/:contactId/next-action

Set, replace, or complete the contact's next action.

POST
/api/projects/:projectId/contacts/:contactId/stage

Move a contact between stage tags.

POST
/api/projects/:projectId/contacts/:contactId/enrich

Run contact research and return the enriched contact.

GET
/api/projects/:projectId/contact-views

List saved contact views.

POST
/api/projects/:projectId/contact-views

Create a saved contact view.

PUT
/api/projects/:projectId/contact-views/:viewId

Update a saved contact view.

DELETE
/api/projects/:projectId/contact-views/:viewId

Delete a saved contact view.

POST
/api/projects/:projectId/pipeline/seed

Create the default pipeline stages when missing.

  • Contact listing defaults to 50 records and returns { contacts, total }; use limit and offset to load more.
  • Supported filters include search, tagId, repeated tagIds, matchAllTags, stageTagId, repeated excludeStageTagIds, activityType, activitySinceDays, noActivitySinceDays, and bookingStatus.
  • Contact writes support name, email, phone, notes, metadata, company, companyWebsite, position, companySize, estimatedRevenue, and linkedinUrl. Set next action with { text, deadline }; send both as null to complete it.

Contact list filters

PropertyTypeRequiredDescription
searchstringNoMatch contact name or email
tagIdstringNoLegacy single-tag filter
tagIdsstring[]NoRepeat the query key to filter by multiple tags
matchAllTagsbooleanNoRequire every tagIds value instead of any
stageTagIdstringNoRequire the current stage tag
excludeStageTagIdsstring[]NoRepeated stage IDs to exclude
activityTypeenumNoFilter by the latest supported activity type
activitySinceDaysnumberNoRequire activity within this many days
noActivitySinceDaysnumberNoRequire no activity within this many days
bookingStatusenumNoFilter by related booking status
limitintegerNoPage size; defaults to 50 and is capped at 100
offsetintegerNoZero-based result offset
Filtered contact pagebash
curl --get "https://linkycal.com/api/projects/proj_123/contacts" \
  -H "Authorization: Bearer lc_live_your_api_key" \
  --data-urlencode "tagIds=tag_lead" \
  --data-urlencode "tagIds=tag_vip" \
  --data-urlencode "matchAllTags=true" \
  --data-urlencode "limit=50" \
  --data-urlencode "offset=0"

Contact activity pagination

Activity accepts category values all, bookings, form_responses, or workflows. The default page size is 20 and the maximum is 100. Pass the returned opaque nextCursor unchanged to load the next page.

Contact timeline pagebash
curl --get "https://linkycal.com/api/projects/proj_123/contacts/ct_123/activities" \
  -H "Authorization: Bearer lc_live_your_api_key" \
  --data-urlencode "category=bookings" \
  --data-urlencode "limit=20"

Tags

Create project tags and assign them to contacts. Names are unique within a project after trimming and case normalization.

GET
/api/projects/:projectId/tags

List tags with search and cursor pagination.

POST
/api/projects/:projectId/tags

Create a tag from name and optional #RRGGBB color.

GET
/api/projects/:projectId/tags/:tagId

Get a tag.

PATCH
/api/projects/:projectId/tags/:tagId

Update a tag's name or color.

DELETE
/api/projects/:projectId/tags/:tagId

Delete an unused tag.

POST
/api/projects/:projectId/contacts/:contactId/tags

Legacy assignment endpoint accepting { tagId }.

PUT
/api/projects/:projectId/contacts/:contactId/tags/:tagId

Assign a tag to a contact idempotently.

DELETE
/api/projects/:projectId/contacts/:contactId/tags/:tagId

Remove a tag from a contact idempotently.

  • List tags with search and optional cursor pagination. A cursor requires the same limit on the next request.
  • PUT assignment and DELETE removal are idempotent: assigned or removed is false when no relationship changed.
  • Deleting a workflow-referenced tag returns 409 TAG_IN_USE with the workflows that block deletion.
  • POST assignment is retained for compatibility; new integrations should use the canonical PUT endpoint.

List and create tags

PropertyTypeRequiredDescription
searchstringNoCase-insensitive name search, 1–100 characters
limitintegerNoOptional page size from 1 to 100
cursorstringNoOpaque nextCursor value; requires limit
namestringYesTrimmed project-unique name, 1–50 characters
colorstringNoOptional six-digit hexadecimal color such as #1B4332
Create a tagbash
curl -X POST "https://linkycal.com/api/projects/proj_123/tags" \
  -H "Authorization: Bearer lc_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"name":"Qualified lead","color":"#1B4332"}'
Paginated tag responsejson
{
  "tags": [
    {
      "id": "tag_123",
      "projectId": "proj_123",
      "name": "Qualified lead",
      "color": "#1B4332",
      "createdAt": "2026-07-22T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}

Assign and remove contact tags

Idempotent assignmentbash
curl -X PUT \
  "https://linkycal.com/api/projects/proj_123/contacts/ct_123/tags/tag_123" \
  -H "Authorization: Bearer lc_live_your_api_key"

Assignment returns { tag, assigned }; removal returns { success, tag, removed }. Repeating either request succeeds and reports false for the unchanged relationship.

Deletion conflictjson
{
  "error": "Tag is referenced by one or more workflows",
  "code": "TAG_IN_USE",
  "workflows": [{ "id": "wf_123", "name": "Research new leads" }]
}

Workflow management

Manage workflow definitions and ordered steps, inspect execution runs, and start manual or test executions.

GET
/api/projects/:projectId/workflows

List workflows.

POST
/api/projects/:projectId/workflows

Create a workflow.

GET
/api/projects/:projectId/workflows/:workflowId

Get a workflow.

PUT
/api/projects/:projectId/workflows/:workflowId

Update a workflow.

DELETE
/api/projects/:projectId/workflows/:workflowId

Delete a workflow.

GET
/api/projects/:projectId/workflows/:workflowId/steps

List workflow steps.

POST
/api/projects/:projectId/workflows/:workflowId/steps

Create a workflow step.

PUT
/api/projects/:projectId/workflows/:workflowId/steps/:stepId

Update a workflow step.

DELETE
/api/projects/:projectId/workflows/:workflowId/steps/:stepId

Delete a workflow step.

PUT
/api/projects/:projectId/workflows/:workflowId/steps/reorder

Reorder workflow steps.

GET
/api/projects/:projectId/workflows/:workflowId/runs

List workflow runs with an optional limit.

GET
/api/projects/:projectId/workflows/:workflowId/runs/:runId

Get one run with step snapshots.

POST
/api/projects/:projectId/workflows/:workflowId/trigger

Start a manual workflow run.

POST
/api/projects/:projectId/workflows/:workflowId/test

Test a workflow against supplied context.

  • Triggers: form_submitted, booking_created, booking_cancelled, booking_pending, booking_confirmed, new_contact_created, tag_added, manual, and scheduled.
  • Step types: send_email, ai_research, add_tag, remove_tag, wait, condition, webhook, and update_contact.
  • Create a workflow with name, trigger, and optional triggerConfig. Steps use sortOrder, type, config, and an optional condition; update calls are partial.

Analytics and recent activity

Read project-level activity and unique-journey booking/form funnels, then configure validated customer analytics providers.

GET
/api/projects/:projectId/activity/recent

Get recent project activity.

GET
/api/projects/:projectId/analytics/filters

Get available analytics filter values.

GET
/api/projects/:projectId/analytics/overview

Get conversion and traffic overview metrics.

GET
/api/projects/:projectId/analytics/bookings

Get booking analytics.

GET
/api/projects/:projectId/analytics/forms

Get form analytics.

GET
/api/projects/:projectId/analytics/integrations

List normalized GA4, Meta Pixel, and PostHog configuration.

PUT
/api/projects/:projectId/analytics/integrations/:provider

Configure one allowlisted analytics provider.

  • Detailed analytics and provider configuration require a Pro or Business project. The Worker enforces entitlement and project scope for sessions, API keys, and MCP.
  • Analytics supports period=7d|30d|90d|custom. Custom requires both inclusive start and end ISO dates; preset periods omit them.
  • Queries may filter by resourceSlug, UTM source/medium/campaign, direct or widget source, and mobile/tablet/desktop device type.
  • Choose one project-owned event type or form for exact stages. All resources remains a backward-compatible high-level summary.
  • Detailed reports count unique journeys and expose availableSince, stage visitors, continuation/drop-off rates, conditional skips, booking date/time availability context, and safe failures.
  • Reports never return names, emails, raw answers, journey IDs, IP addresses, or raw errors.
  • Provider writes accept only public GA4 measurement IDs, numeric Meta Pixel IDs, and PostHog project keys with an allowlisted US/EU host. Raw scripts and arbitrary URLs are rejected.

Files and project calendars

Upload project-owned files and inspect calendars available through the project's connected Google accounts.

POST
/api/projects/:projectId/uploads

Upload a project-owned file.

DELETE
/api/projects/:projectId/uploads/:key

Delete a project-owned upload.

GET
/api/projects/:projectId/calendar/calendars

List calendars available to the project.

  • Uploads use multipart/form-data and return an object key or URL for later use.
  • Deleting an upload requires the complete object key, including nested path segments.
  • Calendar listing is read-only; connect or disconnect Google accounts in the dashboard, then select calendars through the event type endpoints.

Booking Widget

Embed a fully functional booking experience on any page. The widget handles event type selection, date/time picking, and form submission.

Full Embedhtml
<div id="booking-widget"></div>
<script src="https://cdn.linkycal.com/widgets/booking.js"></script>
<script>
  LinkyCal.booking({
    projectSlug: "acme",
    container: "#booking-widget",
    eventTypeSlug: "consultation",
    theme: {
      primaryColor: "#1B4332"
    }
  });
</script>
PropertyTypeRequiredDescription
projectSlugstringYesYour project's URL slug
containerstring | HTMLElementYesCSS selector or DOM element
eventTypeSlugstringNoSkip event type selection
theme.primaryColorstringNoOverride brand color

Form Widget

Embed multi-step forms directly on your site. The widget renders each step in sequence and handles validation, submission, and completion state.

Full Embedhtml
<div id="form-widget"></div>
<script src="https://cdn.linkycal.com/widgets/form.js"></script>
<script>
  LinkyCal.form({
    projectSlug: "acme",
    formSlug: "contact",
    container: "#form-widget",
    theme: {
      primaryColor: "#1B4332"
    }
  });
</script>
PropertyTypeRequiredDescription
projectSlugstringYesYour project's URL slug
formSlugstringYesThe form's URL slug
containerstring | HTMLElementYesCSS selector or DOM element
theme.primaryColorstringNoOverride brand color

Customization

Both widgets accept a theme object to match your brand. Override colors, border radius, and fonts.

Theme Overridejavascript
LinkyCal.booking({
  projectSlug: "acme",
  container: "#booking-widget",
  theme: {
    primaryColor: "#1B4332",
    borderRadius: "12px",
    fontFamily: "Inter, sans-serif"
  }
});
Both widgets are zero-dependency IIFE bundles under 6KB gzipped.

Triggers

Workflows start with a trigger. When the trigger event occurs, all connected actions execute in sequence.

form_submittedFires when a form response is completed
booking_createdFires whenever a new booking is created
booking_cancelledFires when a booking is cancelled
booking_pending / booking_confirmedFires for the corresponding booking state
new_contact_createdFires when a brand-new contact is stored
tag_addedFires when a tag is added to a contact
manualTriggered manually via API
scheduledRuns hourly, daily, weekly, or monthly in the configured timezone

Actions

Actions are the steps executed when a workflow is triggered. Chain multiple actions together with conditions and delays.

send_emailSend email via Resend
ai_researchResearch and enrich a contact with structured results
add_tag / remove_tagModify contact tags
waitDelay execution by a specified duration
conditionIf/else branching based on contact data
webhookHTTP request to an external URL
update_contactModify contact fields

Webhook Events

A webhook step sends the configured method, headers, and body to an external URL. The method defaults to POST; the body defaults to the workflow context below, but you can replace it and interpolate workflow values.

Webhook Payloadjson
{
  "projectId": "proj_123",
  "contactId": "ct_m1n2o3",
  "contactEmail": "jane@example.com",
  "formResponseId": "resp_a1b2c3d4",
  "metadata": {}
}

MCP Server

LinkyCal ships a built-in Model Context Protocol server, so AI agents can check availability, book meetings, manage contacts, and inspect forms on your behalf. It speaks Streamable HTTP at a single endpoint:

MCP Endpointtext
https://linkycal.com/api/mcp

MCP connections authenticate with a project API key passed as a Bearer token. Every tool is hard-scoped to that key's project — agents never pass a project ID and can never reach data outside the project the key belongs to.

Connecting

Create an API key in the dashboard under MCP & APIs, then register the server with your MCP client using the endpoint and authorization header below.

MCP Client Configjson
{
  "mcpServers": {
    "linkycal": {
      "type": "http",
      "url": "https://linkycal.com/api/mcp",
      "headers": {
        "Authorization": "Bearer lc_live_a1b2c3d4e5f6..."
      }
    }
  }
}
The API key grants access to the MCP tools for its project. Configure it in server-side or local agent environments only — never ship it in client-side code. Project and key administration remain dashboard-only.
Once connected, just ask in natural language: “Book a 30-minute demo with Sarah Chen next Tuesday at 2pm” — the agent picks the right tools, checks availability, and confirms the booking.

Available Tools

The server exposes 40 tools for the project, grouped by domain. Read tools return JSON; write tools enforce the same plan limits and validation as the dashboard.

Bookingslist_bookings · get_booking · get_available_slots · create_booking · cancel_booking · confirm_booking · decline_bookingRead and manage bookings and public availability.
Event Typeslist_event_types · get_event_type · create_event_type · update_event_typeDefine bookable meeting types.
Scheduleslist_schedules · get_scheduleInspect the working hours behind event types.
Contactslist_contacts · get_contact · create_contact · update_contact · set_contact_next_action · complete_contact_next_action · delete_contact · get_contact_activityManage CRM records and their activity.
Tagslist_contact_tags · get_contact_tag · create_contact_tag · update_contact_tag · delete_contact_tag · add_tag_to_contact · remove_tag_from_contactManage tags and contact assignments.
Formslist_forms · get_form · create_form · update_form · list_form_responsesBuild forms and inspect aggregate submissions.
Workflowslist_workflows · get_workflowInspect workflows; writes remain REST/dashboard-only.
Analyticsget_analytics_overview · get_booking_funnel_analytics · get_form_funnel_analytics · list_analytics_integrations · configure_analytics_integrationRead aggregate funnels and configure validated GA4, Meta Pixel, and PostHog public identifiers.
Analytics tools use the same project-scoped aggregate actions as REST. Pass an event type ID or form ID for exact stages; common inputs support preset/custom dates, UTM filters, direct/widget source, and device type. Detailed reports and provider configuration require Pro or Business.

get_booking_funnel_analytics and get_form_funnel_analytics return unique-journey stage visitors, continuation, drop-offs, skips, safe context distributions, and the detailed-data boundary. Aggregate outputs exclude names, emails, raw answers, journey IDs, IP addresses, and raw errors. configure_analytics_integration accepts only GA4, Meta Pixel, or PostHog public identifiers and an allowlisted US/EU PostHog host.

Workflows are read-only over MCP by design — agents can inspect automations but not modify them. Booking writes still trigger your workflows, emails, and calendar sync exactly like the API.

API Keys

Create API keys in the dashboard under MCP & APIs. Include your key in the Authorization header as a Bearer token for MCP and protected /api/projects/:projectId/* requests. Visitor endpoints are anonymous and must not receive this header.

Authorization Headerbash
curl -H "Authorization: Bearer lc_live_a1b2c3d4e5f6..." \
  "https://linkycal.com/api/projects/proj_123/contacts"
API keys are project-scoped management credentials. Keep them in server-side secret storage or a local agent configuration. Never expose them in visitor-side code.

API access is available on Pro and Business projects. Free projects return api_access_unavailable even when the key itself is valid. Read GET /api/projects/:projectId/entitlements when your integration needs the current resource limits.

Send either a dashboard session or an API key, never both. If a request has a valid session plus any Authorization header, LinkyCal returns ambiguous_credentials. An invalid Bearer value returns invalid_api_key and never falls back to a session. A key used with another project returns api_key_project_mismatch; a project without current API entitlement returns api_access_unavailable.

Rate Limits

The API enforces per-IP rate limits to ensure fair usage. If you exceed the limit, you'll receive a 429 Too Many Requests response.

EndpointLimit
Availability checks60 requests/minute per IP
Booking creation10 requests/minute per IP
Form responses30 requests/minute per IP
Form response uploads30 requests/minute per IP
Form step submissions60 requests/minute per IP
These per-IP limits apply to visitor endpoints. Protected project requests are additionally constrained by project ownership, current plan, and resource limits.

Error Handling

All errors return a JSON object with a descriptive error field. Use the HTTP status code to determine the type of error.

Error Responsejson
{
  "error": "Descriptive error message"
}

Common status codes:

400Validation error — check your request body or parameters
401Unauthorized — missing or invalid API key
403Forbidden — plan limit reached or feature not available
404Not found — resource does not exist
409Conflict — a unique value already exists or a referenced resource cannot be deleted
429Rate limited — too many requests, try again later
500Server error — something went wrong on our end

Pagination

Pagination is endpoint-specific. Do not assume that every list uses the same model.

Contacts

Offset pagination with limit and offset. The default limit is 50, the maximum is 100, and the response includes the full filtered total.

Contact activity

Cursor pagination with a default limit of 20 and maximum of 100. Pass the returned opaque nextCursor unchanged.

Tags

Optional cursor pagination from 1 to 100 items. When using cursor, repeat the same limit on the next request.

Other lists

Other list operations currently return their complete result unless their section or OpenAPI operation declares a limit.

Webhooks

Configure webhooks via workflow actions to receive real-time notifications about events in your project. Create a workflow with a trigger (e.g. form_submitted) and add a webhook action pointing to your URL.

Example: Workflow with Webhookjson
{
  "trigger": "booking_created",
  "actions": [
    {
      "type": "webhook",
      "config": {
        "url": "https://your-app.com/webhooks/linkycal",
        "method": "POST",
        "headers": {
          "X-Webhook-Secret": "your_secret"
        }
      }
    }
  ]
}
Always verify webhook payloads using a shared secret to ensure requests originate from LinkyCal.

Ready to integrate?

Build visitor forms and booking flows without a secret, then use a project key for trusted management integrations.