--- title: Uploading meeting transcripts | Lightfield description: Upload a transcript file and attach it to a meeting in Lightfield. --- This guide builds on [Uploading files](/using-the-api/file-uploads/index.md). A meeting transcript in Lightfield is a file upload that you attach to a meeting through the meeting API. ## Before you begin You will need: - A valid [API key](/using-the-api/api-keys/index.md) - The `files:create` scope to upload and complete the transcript file - Either `meetings:create` or `meetings:update`, depending on whether you are creating a new meeting or updating an existing one - Optional `files:read` and `meetings:read` scopes if you want to verify the final attachment Upload and complete the file first. The meeting transcript relationship expects a completed file, not a pending upload session. ## How transcript attachment works There are two supported patterns: | Pattern | When to use | Meeting request | | ---------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------- | | Attach on create | You are creating the meeting and transcript together | `POST /v1/meetings` with `relationships.$transcript` | | Attach on update | The meeting already exists, or you want to replace the transcript later | `POST /v1/meetings/{id}` with `relationships.$transcript.replace` | In both patterns, the first three steps are identical to the [file upload lifecycle](/using-the-api/file-uploads/index.md): 1. `POST /v1/files` 2. `PUT ` 3. `POST /v1/files/{id}/complete` After that, you attach the completed file to the meeting. ## Option A: create a meeting with a transcript attached First, complete the transcript file upload using [Uploading files](/using-the-api/file-uploads/index.md). Then create the meeting and set `relationships.$transcript` to the completed file id. Pass a single organizer email in `$organizerEmail`. Terminal window ``` curl https://api.lightfield.app/v1/meetings \ -X POST \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Lightfield-Version: 2026-03-01" \ -H "Content-Type: application/json" \ -d '{ "fields": { "$title": "Customer Call", "$startDate": "2026-04-07T21:00:00.000Z", "$endDate": "2026-04-07T21:45:00.000Z", "$description": "Q2 renewal call with Acme", "$meetingUrl": "https://meet.google.com/abc-defg-hij", "$organizerEmail": "alex@acme.com", "$attendeeEmails": ["alex@acme.com", "jamie@example.com"], "$privacySetting": "FULL" }, "relationships": { "$transcript": "fil_abc123" } }' ``` Use this pattern when you want the meeting and transcript linked in one logical flow. ## Option B: attach or replace a transcript on an existing meeting If the meeting already exists, upload and complete the transcript file first. Then update the meeting by setting `relationships.$transcript.replace`. This works for both manual and already-synced meetings as long as the caller is allowed to edit the meeting. Terminal window ``` curl https://api.lightfield.app/v1/meetings/$MEETING_ID \ -X POST \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Lightfield-Version: 2026-03-01" \ -H "Content-Type: application/json" \ -d '{ "fields": { "$privacySetting": "METADATA" }, "relationships": { "$transcript": { "replace": "fil_abc123" } } }' ``` The transcript attachment is driven by `relationships.$transcript.replace`. The example above also updates `$privacySetting` in the same request because that field is writable on meeting updates as well. Replacing a transcript updates the meeting relationship, but it does not delete the previous file automatically. ## End-to-end flow ### Create meeting with transcript 1. `POST /v1/files` 2. `PUT ` 3. `POST /v1/files/{id}/complete` 4. `POST /v1/meetings` with `relationships.$transcript = fileId` 5. Optional: `GET /v1/meetings/{id}` 6. Optional: `GET /v1/files/{id}/url` ### Attach or replace transcript on an existing meeting 1. `POST /v1/files` 2. `PUT ` 3. `POST /v1/files/{id}/complete` 4. `POST /v1/meetings/{meetingId}` with `relationships.$transcript.replace = fileId` 5. Optional: `GET /v1/meetings/{meetingId}` 6. Optional: `GET /v1/files/{id}/url` ## Verifying the attachment After attaching the transcript, retrieve the meeting to confirm the relationship if the caller has access to the transcript under the meeting’s privacy rules. See [Meeting privacy and transcript visibility](#meeting-privacy-and-transcript-visibility) below for what different callers can actually see. Terminal window ``` curl https://api.lightfield.app/v1/meetings/$MEETING_ID \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Lightfield-Version: 2026-03-01" ``` If you also want to confirm transcript retrieval, request a signed download URL for the file: Terminal window ``` curl https://api.lightfield.app/v1/files/$FILE_ID/url \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Lightfield-Version: 2026-03-01" ``` ## Meeting access model | Term | Meaning | | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `manual meeting` | A meeting created directly in Lightfield rather than imported from calendar sync. | | `synced meeting` | A meeting backed by synced calendar data. | | `calendar event` | A synced event record from a user’s connected Google or Microsoft calendar. | | `connected account` | A user’s Google or Microsoft account connection in Lightfield, used for sync and privacy settings. | | `participant` | A caller counts as a participant if one of their connected-account email addresses appears among the meeting attendees, or, for synced meetings, one of the meeting’s linked calendar events has that user’s `userId`. | | `$privacySetting` | The privacy level explicitly set on the meeting itself. | | `accessLevel` | The caller-specific visibility returned on a meeting response. | | `FULL` | Full meeting content is visible, including the transcript relationship. | | `METADATA` | The meeting is visible, but sensitive fields and the transcript relationship are redacted. | | `transcript` | The meeting transcript relationship exposed in the API as `$transcript`. | | `edit access` | Separate from read access; only admins and participants can update a meeting. | ## Meeting privacy and transcript visibility Meeting retrieval is privacy-filtered. In practice, callers will see one of two resolved access levels: | Access level | What the caller can see | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | | `FULL` | Full meeting content, including the real title, description, meeting URL, organizer and attendee emails, and the `$transcript` relationship | | `METADATA` | A metadata-safe view of the meeting; sensitive content is redacted and `relationships.$transcript` is omitted | When a caller only has `METADATA` access: - `$title` is replaced with a metadata-safe synthesized title - `$description` becomes `null` - `$meetingUrl` becomes `null` - `$meetingPrep` becomes `null` - `$postMeetingSummary` becomes `null` - `relationships.$transcript` is removed from the response This means a transcript can be successfully attached to a meeting even if a later `GET /v1/meetings/{id}` response does not show the transcript relationship for a particular caller. ## Privacy setting vs access level `fields.$privacySetting` is the meeting’s stored privacy policy. `accessLevel` is the caller-specific visibility the API resolves at read time. These are related, but not identical. For example, a meeting can have `$privacySetting = METADATA`, while an admin or meeting participant still receives `accessLevel = FULL`. ## Who gets `FULL` access Admins and meeting participants always get `FULL` access. A caller counts as a participant if either: - one of their connected account email addresses appears among the meeting attendees - for synced meetings, one of the meeting’s linked calendar events has that user’s `userId` Other callers receive the meeting’s resolved privacy setting. ## Editing is stricter than viewing Updating a meeting is more restrictive than viewing it. To update a meeting, the caller must be an admin or a participant. Metadata-only visibility is not enough to attach or replace a transcript. Creating a meeting does not, by itself, guarantee that the same caller can update it later. If you want a non-admin caller to be able to update the meeting afterward, make sure they qualify as a participant under the rules above. If a caller can read a meeting in metadata-only form but is not allowed to edit it, `POST /v1/meetings/{id}` will return `403 Forbidden`. ## Next steps - **[Uploading files](/using-the-api/file-uploads/index.md)** — Reference guide for the underlying file upload lifecycle. - **[Objects in Lightfield](/objects-in-lightfield/object-types/index.md)** — Learn how meetings relate to other records in Lightfield. - **[API Reference](/api/index.md)** — Full endpoint reference for files, meetings, and other resources.