DineGlobeDocs

CSV import

Bulk-import future bookings: the exact file format, every column's rules, the dry-run validation report and the two-step commit.

Manager

CSV import moves a batch of future bookings — for example a list captured by phone or by another diary — into your venue through the same allocator that books live intake. It runs as two explicit acts: stage (the dry run, where every row validates or carries its own error code and nothing is booked) and commit (each valid row books for real or fails with its code). You drive both from the import panel in the operator console. Importing requires the data.import permission — managers and above for their own venue; hosts, door, finance and read-only roles never stage or commit imports.

Before you start

Gather four things besides the file itself:

Input Rules
Venue id A venue you manage.
Offering id An active offering of that venue — every imported row books onto this offering’s slot grid (see Offerings & schedules). Anything else reads as not-found.
Source name One word of letters, digits, dots or dashes (at most 64 characters). It is the namespace for your external ids, so two different systems can each use id-1001 without colliding.
Time zone The IANA zone the file’s wall-clock times are written in.
Limit Value
Encoding UTF-8 only (a byte-order mark is tolerated and stripped). Corrupted encodings are refused before anything is stored.
File size At most 10 MB (10,000,000 bytes).
Data rows At most 10,000 rows, header row excluded.
Structure A header row first; a bounded RFC 4180 dialect — quoted fields, doubled quotes, embedded commas or newlines, LF or CRLF line endings.

The file format

You do not have to rename your columns. Instead you supply a mapping: for each canonical field below, you name which of your file’s headers supplies it. Columns you do not map are ignored, and mapping names must match your header row exactly.

Canonical field Required Rules
external_id Yes Your system’s own id for the booking, at most 120 characters. It is the import’s identity: the same id can never become a second booking.
date Yes YYYY-MM-DD, a real calendar date — the service business date.
time Yes 24-hour wall-clock time (for example 19:30). It must resolve to exactly one instant in the declared time zone.
party_size Yes Whole number from 1 to 200.
display_name Yes The guest’s display name, 1–160 characters.
email No A plausible address, at most 254 characters; stored lowercased.
phone No E.164 format with a leading + and country code.

There is deliberately no notes column. Imported free-text notes would be sensitive data arriving over a bulk channel, so guest notes stay on the normal notes contract with its encryption and role-based redaction.

Step 1 — stage the dry run

Staging pastes the CSV in and never touches the book. The server decodes the file, parses it, checks your mapping against the header, validates every row against the typed rules above, and then runs an advisory availability read against the offering’s slot grid. The report you get back shows the job id, its STAGED state, total/valid/rejected row counts, a SHA-256 fingerprint of the exact file (shortened in the panel), and one line per rejected row with its codes.

Nothing is booked until the commit below — a staged report is a preview, and you can discard it by simply never committing.

File-level refusals

These refuse the whole file before any row is stored:

Code Remedy
IMPORT_TOO_LARGE The file exceeds 10 MB — split it and retry.
IMPORT_ENCODING The file is not valid UTF-8. Re-export it as UTF-8.
IMPORT_CSV_EMPTY The file has no header row.
IMPORT_CSV_MALFORMED The CSV structure is broken — usually a stray quote after a closed cell or an unterminated quoted field. Fix the quoting.
IMPORT_HEADER_MISSING A mapped canonical field names a column the file does not have. Correct the mapping to match the file’s actual headers (the field is named in the error).
IMPORT_TOO_MANY_ROWS More than 10,000 data rows — split into batches.

Row-level rejections

Each rejected row carries its own codes; everything else stays valid.

Code Remedy
REQUIRED_FIELD A required column is empty in this row (the field is named).
EXTERNAL_ID_TOO_LONG Shorten the external id to 120 characters or fewer.
INVALID_DATE Not a real YYYY-MM-DD calendar date.
INVALID_TIME Not a readable 24-hour time.
DST_GAP / DST_AMBIGUOUS The wall-clock time falls into a daylight-saving gap or is ambiguous on that date. Pick a time that resolves to one instant.
PAST_START The resolved start is already in the past — imports bring in future bookings only.
INVALID_PARTY Party size is not a whole number from 1 to 200.
INVALID_NAME The display name exceeds 160 characters.
INVALID_EMAIL / INVALID_PHONE The optional contact does not match the address or E.164 shape.
DUPLICATE_EXTERNAL_ID The same external id appears twice in this file; the later row is rejected.
EXTERNAL_ID_ALREADY_IMPORTED An id from this source has already committed in an earlier job. Each id imports once.
HORIZON_EXCEEDED The date lies beyond the offering’s booking horizon.
CLOSED_DAY The offering has no service window that day (a schedule or exception closed it).
OFF_GRID The time does not sit on any open slot of the current schedule. Align to bookable times.
SLOT_UNAVAILABLE The slot exists but is full, paced out, or otherwise unfittable. An open slot with a notice lead time stays importable.

Rows whose party size falls outside the offering’s accepted range are rejected with that availability refusal’s code, named on the party_size field.

Step 2 — commit

The commit is its own explicit act: you pass the venue id and the import job id. Every still-valid row then books through the same booking command as live intake — the real venue transaction, day rules, governing policy, physical table selection and database constraints all re-run under the lock. That is the point of the design: a conflict that appeared between the dry run and the commit becomes an explicit failed row, never a silent disappearance and never a bypass.

What can happen to a row at commit time:

  • Committed. The row books, and the report links its reservation id and confirmation code.
  • Failed with its code. The allocator refused that row and only that row; the job continues with the rest. Recurring causes: the grid moved since the dry run (IMPORT_OFF_GRID), a slot conflict appeared (SLOT_UNAVAILABLE), the schedule revision changed (AVAILABILITY_STALE), or the disclosed terms moved (POLICY_STALE).
  • Refused as paid. A row whose governing policy charges money is rejected with IMPORT_PAID_POLICY — imported bookings are never charged (see Booking policies).
  • Rejected as a duplicate. If another job already committed the same source-and-external-id pair, the row is rejected with EXTERNAL_ID_ALREADY_IMPORTED — the database enforces the claim, so even two imports racing on the same id produce exactly one booking.

Rows already rejected at staging stay rejected; the job’s final counts split committed versus rejected (rejected counts staging rejections plus commit failures).

The commit is checkpointed and replay-safe. The booking’s request key derives from your source name plus the row’s external id, so a crash between the booking write and the row write replays the same reservation on resume instead of booking twice. The acting identity is the job’s original requester, so a second manager can resume an interrupted checkpoint without breaking that replay. And once a job is COMPLETED, rerunning the commit just replays its stored summary — it never makes a second pass over the rows.

After the commit

Imported contacts become guest profiles through the normal booking path and nothing else: the import never writes consent evidence, so no marketing consent is ever fabricated from a CSV. Imported bookings enter the ordinary confirmation pipeline, which in this pilot composes messages but does not actually send email — staff see the honest failed-delivery evidence instead of a sent message (see Messages, confirmations & reminders).

Single-market pilot evidence only: one approved country and its listed capabilities do not constitute global support; unlisted countries and capabilities stay disabled.

Quick error index

File-level codes (IMPORT_* on the stage form) are fixed by correcting the file or mapping; row-level codes are fixed by correcting the named rows and staging a fresh file (a staged job never changes after the fact); commit-time codes land on individual rows as failures and are resolved by reimporting those rows under new external ids or at corrected times. The general taxonomy — VALIDATION, NOT_FOUND, PERMISSION_DENIED, UNAUTHENTICATED — behaves as described in Error codes & fixes, and Troubleshooting covers the scene-by-scene recoveries.