Last updated October 25, 2026
The JourneyFuse API lets outside tools read and create data in your workspace automatically. Where Webhooks send data out of JourneyFuse when something happens, the API lets you send data in — for example, pushing a new Facebook lead into JourneyFuse the moment it comes in, with no manual copying — and read it back, so an assistant or dashboard can answer questions about your book of business.
If you use Make.com, Zapier, or your own code to move data between tools, this is how you connect them to JourneyFuse.
Using Zapier? Start with the JourneyFuse Zapier app. It handles the API calls for you, so you pick triggers and actions from a menu instead of building HTTP requests.
The API covers the most common automation needs:
| Action | Method | Endpoint |
|---|---|---|
| Verify your key / get workspace info | GET | /api/v1/me |
| Create a lead | POST | /api/v1/leads |
| Create a client | POST | /api/v1/clients |
| Tag a client or lead | POST/PATCH | send tags on any create or update below |
| Find clients (by email, or list recent) | GET | /api/v1/clients |
| Get a client by ID | GET | /api/v1/clients/:id |
| Update a client | PATCH | /api/v1/clients/:id |
| Create a trip | POST | /api/v1/trips |
| List trips | GET | /api/v1/trips |
| Get a trip by ID | GET | /api/v1/trips/:id |
| List all travelers on a trip | GET | /api/v1/trips/:id/travelers |
| Read group checkout selections | GET | /api/v1/proposal-bookings and /api/v1/proposal-bookings/:id |
| List or download generated documents | GET | /api/v1/documents?type=invoice |
| Read card authorization records and receipts | GET | /api/v1/card-authorizations |
| Register, list, or remove webhooks | POST / GET / DELETE | /api/v1/webhooks and /api/v1/webhooks/:id |
| Update a trip | PATCH | /api/v1/trips/:id |
| List leads | GET | /api/v1/leads |
| Get a lead by ID | GET | /api/v1/leads/:id |
| List bookings | GET | /api/v1/bookings |
| Get a booking by ID | GET | /api/v1/bookings/:id |
| List quotes (proposals) | GET | /api/v1/proposals |
| Get a quote by ID | GET | /api/v1/proposals/:id |
| Ask a client to fill in their own details | POST | /api/v1/clients/:id/info-request |
| Create a client and a trip in one call | POST | /api/v1/intake (also /api/v1/bookings) |
| Copy your whole supplier directory, hidden cards, rates and Booking notes included | GET | /api/v1/suppliers |
The base URL is https://journeyfuse.com (or your agency's custom domain, if you have one).
Everything you can create, you can now read back. Writing is still limited to leads, clients and trips: there are no write endpoints for individual booking components (hotels, flights, cruises) or for quotes.
Wiring an AI assistant (Grok, ChatGPT, Claude) to your account? Start with Connect an AI Assistant to JourneyFuse, which covers the setup and what to know before handing a key to an outside service.
GET endpoint and subscribe to webhooks, but any request that would create or change data is refused with a 403.jf_live_)Your full key is shown only once, right after you create it. Copy it somewhere safe before closing the dialog. If you lose it, just create a new one and delete the old.
Treat a key like a password. You can revoke one at any time from the same page.
Every key belongs to one workspace, and how much of that workspace it reaches depends on who created it.
| Who creates it | Scope | What the key can reach |
|---|---|---|
| An advisor on a team | Advisor only | That advisor's own clients, leads, trips, bookings, and quotes |
| An agency owner or admin | Agency-wide | Every record in the workspace |
| A solo agency (one member) | Agency-wide | Everything, since it is all yours anyway |
This matters most at a host agency, where hundreds of independent advisors share one JourneyFuse workspace. An advisor's key can never read a colleague's clients, and a lookup for someone else's client comes back empty rather than telling you it exists somewhere.
If you are on a team, your key behaves like this:
404 even with the correct ID. Bookings and quotes follow the advisor their trip belongs to, since that is who owns them.agent_email and agent_id are accepted so the same request body works everywhere, but leads always land on you. Use an agency-wide key to route leads to other advisors.Owners and admins see every key in the workspace on the Settings → API Keys page, labeled Advisor only or Agency-wide. Advisors see only their own.
Send your key on every request, in either of these headers:
Authorization: Bearer jf_live_your_key_here
or
x-api-key: jf_live_your_key_here
To check that a key works, call the me endpoint:
curl https://journeyfuse.com/api/v1/me \
-H "Authorization: Bearer jf_live_your_key_here"
A working key returns your workspace:
{
"workspace": { "id": "...", "name": "Your Agency", "kind": "agency" },
"scopes": ["*"]
}
This is the most common use: pushing a new inquiry into JourneyFuse so it shows up in your Leads pipeline at stage New.
Endpoint: POST /api/v1/leads
Body fields:
| Field | Required | Notes |
|---|---|---|
first_name | Yes* | Required unless you send name |
last_name | Yes* | Required unless you send name |
name | Yes* | Alternative to the pair above. A single full name, split on the first space — "Jamie Rivera" becomes first Jamie, last Rivera. Also accepted as full_name |
email | No | |
phone | No | |
source | No | Where the lead came from, e.g. "Facebook Lead Ads" |
destination | No | Where they want to travel |
notes | No | Any extra detail from the form |
reference | No | Your own reference for the enquiry (for example a website enquiry code). Searchable in JourneyFuse, and copied to the trip's internal reference when the lead becomes a trip. You can also filter with GET /api/v1/leads?reference=… |
travel_start_date, travel_end_date | No | Travel dates, YYYY-MM-DD |
adults, children | No | Party size, whole numbers |
child_ages | No | Children's ages, as a list ([5, 11]) or text ("5, 11") |
budget | No | A number, no currency symbol |
travel_type | No | The kind of trip, e.g. Beach or Cruise |
referred_by_name | No | Who referred them |
city, state, zip, country | No | Where they live |
agent_email | No | Route the lead to a specific agent by their JourneyFuse email |
agent_id | No | Route the lead to a specific agent by their user ID |
*Send either first_name + last_name or name. If you send name
along with an explicit first_name or last_name, the explicit field wins.
If name is a single word ("Prince"), the lead is stored with a blank last
name. This is what makes the endpoint work with chat platforms like ManyChat,
Messenger and Instagram, where contacts frequently have no Last Name.
Example request:
curl -X POST https://journeyfuse.com/api/v1/leads \
-H "Authorization: Bearer jf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Jamie",
"last_name": "Rivera",
"email": "jamie@example.com",
"phone": "+1 555 123 4567",
"source": "Facebook Lead Ads",
"destination": "Maui",
"notes": "Honeymoon, late September, budget around $8k",
"agent_email": "rebecca@youragency.com"
}'
Example request — chat platforms (ManyChat, Messenger, Instagram):
These platforms usually expose one name field, or a Last Name that is often
empty. Map the whole name into name and the request keeps working either way:
curl -X POST https://journeyfuse.com/api/v1/leads \
-H "Authorization: Bearer jf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "{{first_name}} {{last_name}}",
"email": "{{email}}",
"phone": "{{phone}}",
"source": "ManyChat"
}'
If the contact's Last Name is empty, name arrives as just their first name and
the lead is created with a blank last name — no error, no failed automation.
Successful response (201 Created):
{
"data": {
"id": "a1b2c3d4-...",
"first_name": "Jamie",
"last_name": "Rivera",
"email": "jamie@example.com",
"phone": "+1 555 123 4567",
"source": "Facebook Lead Ads",
"stage": "new",
"destination": "Maui",
"assigned_to": "9a26f4ee-...",
"assignment_status": "pending",
"created_at": "2026-06-17T14:30:00.000Z"
},
"assignment": { "requested": "rebecca@youragency.com", "matched": true }
}
The new lead appears in your Leads pipeline immediately, and a lead.created webhook fires if you have one set up.
This applies to agency-wide keys. On an advisor-scoped key these fields are ignored and the lead is assigned to you (see Key Scope).
Pass agent_email and/or agent_id to route an incoming lead straight to a specific agent. The identifier is matched against your agency's members (email match is case-insensitive):
assignment.matched is false, so your automation can flag it.If you send both agent_email and agent_id, the ID is tried first and the email is used as a fallback.
This walks through the exact setup for sending Facebook lead-ad leads straight into JourneyFuse, replacing a tool like ClickUp in the middle.
https://journeyfuse.com/api/v1/leadsPOSTAuthorizationBearer jf_live_your_key_hereapplication/json{
"first_name": "{{first_name}}",
"last_name": "{{last_name}}",
"email": "{{email}}",
"phone": "{{phone_number}}",
"source": "Facebook Lead Ads",
"destination": "{{destination}}"
}
That is the whole flow. Every new Facebook lead now lands in JourneyFuse automatically, tagged with the source so you know where it came from. The same HTTP module approach works for any tool that can send an HTTP request, including Zapier's Webhooks by Zapier → POST action.
Endpoint: POST /api/v1/clients
Required: first_name, last_name. Optional: email, phone, household_id. If you leave household_id out, a household is created automatically (the same as adding a client in the app).
curl -X POST https://journeyfuse.com/api/v1/clients \
-H "Authorization: Bearer jf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{ "first_name": "Sam", "last_name": "Lee", "email": "sam@example.com" }'
Every field on the New Client form can be set here too, so a client created by an automation arrives complete instead of needing the rest typed in by hand. All of these are optional, and anything you leave out is simply left blank.
| Field | Notes |
|---|---|
prefix, middle_name, suffix | Name parts alongside first_name / last_name |
date_of_birth | YYYY-MM-DD. Also accepted as birthday |
anniversary | YYYY-MM-DD |
gender | |
notes | Added to the client's Notes, as a note you can edit in the app afterwards |
address_1, address_2, city, state, zip, country | address also works for address_1, and postal_code for zip |
phone_home, phone_work, phone_mobile, phone_alt | Typed numbers, alongside the main phone |
passport_name, passport_number, passport_expiry, passport_issue_date, passport_country_of_issue, gender_on_passport | The passport profile. Dates are YYYY-MM-DD. passport_country also works for passport_country_of_issue |
place_of_birth, nationality | |
known_traveler_number, redress_number, tsa_precheck_expiry, global_entry_expiry | Trusted-traveler details |
external_id | Your own system's id for this person, for reconciliation |
curl -X POST https://journeyfuse.com/api/v1/clients \
-H "Authorization: Bearer jf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Sam",
"last_name": "Lee",
"email": "sam@example.com",
"date_of_birth": "1985-03-02",
"address": "12 Harbour Way",
"city": "Little Rock",
"state": "AR",
"zip": "72201",
"passport_number": "X1234567",
"passport_expiry": "2031-06-30",
"notes": "Prefers window seats. Celebrating 20th anniversary in 2027."
}'
A notes value is added to the client's Notes, the same list the Notes button on their record writes to, so an advisor can edit or delete it like any other note. Sending the same text again on a later call does not add a second copy, so an automation that re-syncs a record is safe to run as often as you like.
Anything in the body that is not a field name we accept is reported back rather than silently dropped:
{
"data": { "id": "..." },
"ignored_fields": ["passportNumber", "zip_code"]
}
The request still succeeds, because automation payloads often carry housekeeping keys that were never meant for us. Treat ignored_fields as the place to check a mapping: anything listed there did not save.
Responses carry both names for the few fields whose form label differs from the underlying column, so you can read back exactly what you sent: date_of_birth alongside birthday, address alongside address_1, postal_code alongside zip, and passport_country alongside passport_country_of_issue.
Dates are validated before anything is saved. If one is malformed, or a number is not a number, you get a 400 naming every field that was wrong in that request, so you can fix a mapping in one pass rather than one field per attempt:
{
"error": "invalid_input",
"message": "Invalid value for: date_of_birth, passport_expiry.",
"fields": { "date_of_birth": "invalid_date", "passport_expiry": "invalid_date" }
}
Send a tags array to attach workspace tags as the record is created. Tags are matched by name, case-insensitively, and a name that does not exist yet is created for you, so an automation step only ever has to carry the text you would type in the app.
curl -X POST https://journeyfuse.com/api/v1/clients \
-H "Authorization: Bearer jf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Sam",
"last_name": "Lee",
"email": "sam@example.com",
"tags": ["Newsletter Signup"]
}'
A comma-separated string works too, which is what most no-code builders emit from a multi-select: "tags": "Newsletter Signup, VIP".
tags is accepted on POST /api/v1/clients, PATCH /api/v1/clients/:id, POST /api/v1/leads, and POST /api/v1/intake. The response echoes back the tags that were applied. A few things worth knowing:
PATCH adds tags, it never replaces them. There is no way to remove a tag through the API, on purpose: a nightly sync that stopped sending a tag would otherwise strip one an advisor applied by hand./intake, tags go to the client, whether that call created the client or matched an existing one on email. A repeat signup tags the person already on file./leads, tags are applied before your automations run, so a journey that triggers on a tag fires for a lead created through the API.If you are capturing newsletter signups on your own website, note that a JourneyFuse form does this and more without any code: it can tag the submitter and drop them into Marketing Contacts for tag-targeted campaigns in one step. The API is the right tool when the signup has to stay on a page you control.
Endpoint: GET /api/v1/clients
List the most recent clients, or look one up by email:
# Most recent (up to 100 per page; default 20)
curl "https://journeyfuse.com/api/v1/clients?limit=50" \
-H "Authorization: Bearer jf_live_your_key_here"
# Find by email
curl "https://journeyfuse.com/api/v1/clients?email=sam@example.com" \
-H "Authorization: Bearer jf_live_your_key_here"
Every list works the same way: newest first, limit up to 100 per page, and the
filters below. Each also has a matching /:id endpoint that returns one full
record.
# Trips — filter by client, status or kind
curl "https://journeyfuse.com/api/v1/trips?status=planning" \
-H "Authorization: Bearer jf_live_your_key_here"
curl "https://journeyfuse.com/api/v1/trips?client_id=<client-id>" \
-H "Authorization: Bearer jf_live_your_key_here"
# Leads — filter by stage or email
curl "https://journeyfuse.com/api/v1/leads?stage=new" \
-H "Authorization: Bearer jf_live_your_key_here"
# Bookings — the hotels, flights and cruises on a trip
curl "https://journeyfuse.com/api/v1/bookings?trip_id=<trip-id>" \
-H "Authorization: Bearer jf_live_your_key_here"
# Quotes — filter by trip or status
curl "https://journeyfuse.com/api/v1/proposals?status=sent" \
-H "Authorization: Bearer jf_live_your_key_here"
| Endpoint | Filters |
|---|---|
/api/v1/trips | client_id, status, kind |
/api/v1/leads | stage, email |
/api/v1/bookings | trip_id, status, booking_type |
/api/v1/proposals | trip_id, status |
A filter value we do not recognise returns an empty list rather than an error, so polling for a status is safe.
/api/v1/proposals is the endpoint for what your clients see called a quote.
The app, this article and the API all say "proposal" for the same thing. Each
one comes back with a share_url, which is the exact link your client opens,
on your own custom domain when you have one, so you can text it or drop it into
your own confirmation page.
GET and POST on /api/v1/bookings are not the same resource. GET
returns real booking components: the hotel, the flight, the cruise, the rows
that hang off a trip and carry the money. POST on that path is an older alias
for /api/v1/intake, which creates a client and a trip. Both keep working,
but for new automations use /api/v1/intake when you mean intake, and read
bookings here.
There is no write endpoint for booking components. Add those in the app or through AI Import.
# Newest first; filter to one form and/or a start date
curl "https://journeyfuse.com/api/v1/form-submissions?form_id=<form-id>&since=2026-10-01" \
-H "Authorization: Bearer jf_live_your_key_here"
Each record carries the form's name, submitted_at, a submitter (name, email, phone) and an answers list with every question's label and answer. It is the same record the Form Submitted webhook sends, so you can backfill with this and keep up to date with the webhook. Spam is left out, and an advisor key sees only the submissions assigned to that advisor. Your form's ID is in its address when you open it under Forms.
GET /api/v1/trips/:id/travelers returns the complete saved trip participant list, including Guest 2 and beyond. Each entry has id (participant ID), traveler_id (person ID), client_id when linked, first_name, middle_name, last_name, email, phone, date_of_birth, and created_at. Missing contact details remain null. It does not return everyone in the purchaser's household or assume the purchaser is traveling. An empty manifest returns data: [].
For group checkout records, use:
GET /api/v1/proposal-bookings?proposal_id=YOUR_PROPOSAL_UUID&limit=100
GET /api/v1/proposal-bookings?trip_id=YOUR_TRIP_UUID
GET /api/v1/proposal-bookings/CHECKOUT_UUID
These are the actual checkout records, distinct from hotel/cruise/flight components at /api/v1/bookings. Optional filters are proposal_id, trip_id, and status. The list uses limit and cursor as described below. Records must have a linked trip and a non-deleted proposal; records without a linked trip are not returned. Advisor keys follow the checkout's own trip assignment, not the master group trip.
Each record includes the selected option_id, cabin_category, room_label, saved pricing scenario/occupancy labels, adult/teen/child/infant counts, party_size, stored calculated_total, payment status, and addons:
{
"addon_id": "ADDON_UUID",
"title": "CAS Pass",
"pricing_type": "per_person",
"quantity": 1,
"inventory_quantity": 2,
"traveler_indices": [0, 1]
}
quantity is the saved selection quantity. inventory_quantity is the inventory consumed; these can differ, especially for whole-party extras. Older selections with no saved quantity return null, not a guessed value. Names and pricing types describe the current add-on definition; stored totals and quantities are not recalculated from today's prices.
traveler_indices map to this checkout's traveler_slots array ({index, name}), with the lead at index 0. They are not indexes into the trip participant response. A missing name remains null.
occupancy.label is the saved occupancy selection, with source: "saved_selection". Older checkouts that never saved it return label: null and source: "not_recorded". Party size remains available, but is not treated as room occupancy because a party may occupy multiple rooms.
A trip fetched by ID exposes travelers_url and checkout_selections_url. Booking component responses also expose checkout_selections_url. These are relative API paths and require the same API key. Group records have separate trip IDs for each booked party, so read the travelers_url on each checkout to retrieve that party's guests.
For individual proposal acceptance, /api/v1/proposals and /:id additionally return accepted_option_ids, accepted_addon_ids, accepted_addon_selections (saved quantity, traveler names and amount when recorded), and accepted_room_label.
All of these reads and downloads require the API key in the request header. The URLs are authenticated API paths, not public sharing links. Keep the key on your backend rather than exposing it in a public dashboard or download URL.
GET /api/v1/documents?type=invoice
GET /api/v1/documents?type=payment_receipt&trip_id=TRIP_UUID
GET /api/v1/documents?type=signed_agreement&client_id=CLIENT_UUID
GET /api/v1/documents/DOCUMENT_UUID?type=invoice
GET /api/v1/documents/DOCUMENT_UUID/download?type=invoice
type is required on every document request:
| Type | Resource ID | Download |
|---|---|---|
invoice | Invoice ID | The standard JourneyFuse invoice PDF |
payment_receipt | Paid payment ID | Printable HTML receipt showing recorded payment and refund amounts |
signed_agreement | Signed client agreement ID | Printable HTML of the stored agreement text, signer, signing time and text hash |
List responses support limit and cursor. Invoices support trip_id and client_id filters. Payment receipts support trip_id and invoice_id. Signed agreements support client_id. Unsupported filters return 400.
Metadata includes download_url and content_type. A signed agreement with an attached original file also includes attachment_url. That endpoint downloads the original from JourneyFuse storage after checking agreement access and document visibility. Legacy externally hosted attachments return 409 attachment_unavailable rather than redirecting to an unverified external location. These endpoints do not list arbitrary uploaded files or identity documents.
Invoices and payment receipts follow their trip's advisor. Signed agreements follow the linked client's access. Pending agreements and unpaid payments do not appear as signed agreements or payment receipts.
GET /api/v1/card-authorizations?trip_id=TRIP_UUID
GET /api/v1/card-authorizations/AUTHORIZATION_UUID
GET /api/v1/card-authorizations/AUTHORIZATION_UUID/receipt
Authorization lists support limit, cursor, status, trip_id, client_id, booking_id, and payment_id. Responses include status, amount/currency, related record IDs, authorization time, charge status/amount, and card brand/last four. An authorization is not proof of a successful charge; check charge_status and the payment record separately.
receipt_url is available once an authorization has been submitted and returns a printable HTML record. Full card numbers, security codes, encrypted credentials, access tokens, identity-document links, and billing addresses are never included. A trip-linked authorization follows the trip's advisor; for a tripless authorization, only its creator's advisor key (or an agency-wide key) can read it.
Use your normal API key to register a public HTTPS receiver:
POST /api/v1/webhooks
Content-Type: application/json
{
"url": "https://your-dashboard.example.com/journeyfuse-events",
"events": ["booking.created", "authorization.submitted", "document.created"],
"description": "Booking dashboard"
}
The 201 response includes the signing secret once. Store it securely and verify X-JourneyFuse-Signature as HMAC-SHA256 of the exact raw request body. GET /api/v1/webhooks?limit=50&offset=0 lists accessible subscriptions with pagination: {total, limit, offset}; it never returns secrets. DELETE /api/v1/webhooks/:id removes an accessible subscription and returns 204. Advisor keys create and manage advisor-scoped subscriptions; agency keys can manage the workspace's subscriptions.
authorization.submitted means the card authorization submission was saved successfully. document.created announces generated invoice, payment_receipt, or signed_agreement resources, carrying id, type, content_type, and an authenticated download_url. booking.created announces saved booking components, with supplier, confirmation number, start and end dates, booking date, total price, currency, expected commission (commission_amount, commission_rate as a percent), the trip name, and the lead client's first and last name; group checkout events also carry checkout_id and proposal_id so you can read selections and travelers.
Delivery is best effort with retries, not an exactly-once or instant-delivery guarantee. Deduplicate by event/resource identity, acknowledge promptly, and use the read endpoints to reconcile your dashboard. See Webhooks for the delivery envelope, signatures, and retry behavior. Existing Zapier subscription endpoints continue to work.
Record lists can return more records than one page holds. When there are more, the
response carries a next_cursor. (Webhook subscriptions use limit and offset, as described above.) Pass it back as cursor to get the next page,
and repeat until it stops coming back:
curl "https://journeyfuse.com/api/v1/clients?limit=100" \
-H "Authorization: Bearer jf_live_your_key_here"
{
"data": [ ... 100 clients ... ],
"next_cursor": "MjAyNi0wOS0wMVQxMDoxMjozM1p8YzhkOWU..."
}
curl "https://journeyfuse.com/api/v1/clients?limit=100&cursor=MjAyNi0wOS0wMVQxMDoxMjozM1p8YzhkOWU..." \
-H "Authorization: Bearer jf_live_your_key_here"
When next_cursor is absent, you have reached the end.
Two things worth knowing. The cursor is safe to use on a live account: records
created while you are part-way through a walk will not shift the pages under
you, so nothing gets skipped or handed to you twice. And a cursor we did not
issue comes back as a 400 rather than quietly restarting at the first page,
which would otherwise leave a sync looping over the same records and looking
like it was working.
Endpoints: PATCH /api/v1/clients/:id and PATCH /api/v1/trips/:id
Records rarely arrive complete. Passport and trusted-traveler details usually turn up weeks after the first form, sometimes after the deposit, and a PATCH is how an automation adds them to a record that already exists.
Send only the fields you want to change. Anything you leave out keeps its current value, and every field the matching POST accepts works here too.
curl -X PATCH https://journeyfuse.com/api/v1/clients/a1b2c3d4-... \
-H "Authorization: Bearer jf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"passport_number": "X1234567",
"passport_expiry": "2031-04-02",
"known_traveler_number": "TT1234567890"
}'
A few things worth knowing:
null does not clear a field. It is ignored, exactly like leaving the field out. This is deliberate: if a form question gets renamed and your automation starts sending empty values, we would rather drop the update than blank a passport number an advisor typed in by hand. To clear a field, edit the record in the app.400, rather than a 200 and an unchanged record, so a mapping typo shows up on the first call instead of silently doing nothing for a month. When only some of the names are unrecognised the request succeeds and lists them in ignored_fields.status does everything the app does when you change it there: queued client emails for that trip are cancelled or re-evaluated, automations re-run their conditions, and a trip.status_changed webhook fires. Re-sending the status a trip already has changes nothing.404, the same as a record that does not exist. On an advisor-scoped key that means your own clients and trips only.To confirm what landed, GET /api/v1/clients/:id or GET /api/v1/trips/:id returns the full record.
If what you need is passport and trusted-traveler information, there is a path that skips your automation entirely: open the client's record in JourneyFuse and use Request info.
That emails them a secure, pre-filled form covering passport number, expiry and country of issue, date of birth, nationality, Known Traveler / TSA PreCheck number, and redress number, alongside contact details and travel preferences. You tick which sections to ask for. What they submit writes straight onto the client record, and the link expires after 30 days.
For anything covered by sensitive documents, this is usually the better route: the data goes from your client to their JourneyFuse record without passing through your inbox, your form tool, or your CRM.
Endpoint: POST /api/v1/clients/:id/info-request
The same request the button sends, so an intake flow does not have to stop and wait for someone to click. Ask for whole sections, individual fields, or both.
curl -X POST https://journeyfuse.com/api/v1/clients/a1b2c3d4-.../info-request \
-H "Authorization: Bearer jf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"sections": ["passport"],
"fields": ["address_1", "city"],
"message": "One last step before we can ticket your flights."
}'
{
"data": {
"id": "7f0c...",
"url": "https://journeyfuse.com/traveler-info/9b2e...",
"expires_at": "2026-09-16T14:02:11.000Z",
"fields_requested": ["passport_number", "passport_expiry", "..."],
"sent": true,
"reused": false
}
}
The sections are passport, contact, preferences, loyalty, personal, and comms — the same groups the in-app picker shows. A section expands to whatever fields it holds at the time of the call, so if we add a field to the passport block later, your existing integration starts asking for it without any change on your side. Use fields when you want exact control.
Three behaviors worth knowing before you build against it:
send: false returns the link without emailing anyone. Use it when you would rather put the link on your own confirmation page or send it by text than have a second email arrive."reused": true. So an automation that re-syncs on every form edit will not mail your client a fresh link each time. Ask for a different set of fields and you get a new request.400 that names it. A mapping typo fails on your first call instead of quietly requesting a shorter form.The link expires after 30 days, and a client with no email address on file returns a 400 unless you send send: false.
Partly, and it is worth knowing the difference when you explain it to them.
The Request info form above is its own link, sent by email or handed over by you. It is the only place that covers the full profile, including Known Traveler / TSA PreCheck and redress numbers.
Separately, any client with a portal can open a trip there and fill in traveler details for everyone travelling, from the Traveler info banner on the trip. That covers names, dates of birth and passport details, and it writes back to the client record the same way. Two caveats: the passport fields only appear when the trip is marked as international, and Known Traveler and redress numbers are not on that form.
Endpoint: POST /api/v1/trips
Required: name. Optional: client_id, start_date, end_date, destination, status (defaults to planning), source. If you pass a client_id, it must belong to your workspace.
source is free text describing where the trip came from — "Website form", "Referral partner", a campaign name. It's stored on the trip and shown as a chip on the trip's header. Unlike the intake endpoint below, there's no default here: leave it out and the trip simply has no source recorded.
status accepts any of lead, planning, deposited, paid, traveling, closed, lost, and is case-insensitive, so "Planning" works as well as "planning".
curl -X POST https://journeyfuse.com/api/v1/trips \
-H "Authorization: Bearer jf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Rivera Honeymoon - Maui",
"client_id": "a1b2c3d4-...",
"start_date": "2026-09-20",
"end_date": "2026-09-28",
"destination": "Maui"
}'
As with clients, the fields on the New Trip form can all be set at creation:
| Field | Notes |
|---|---|
trip_types | Tag list. An array (["Beach","Honeymoon"]) or a comma-separated string. The presets are Beach, Cruise, Disney, All-Inclusive, Europe, Honeymoon, Adventure, Group, and your own values are allowed. trip_type also works |
departure_city | |
notes | The trip's Notes field |
budget | Number |
group_adults, group_children | Whole numbers. adults and children also work |
deposit_due_date, final_payment_date | YYYY-MM-DD. These drive the automated payment reminder emails |
internal_ref | Internal name. Doubles as Policy # / Confirmation # on the other record kinds below |
portal_enabled | true to make the trip visible in the client portal |
Two fields are deliberately not settable here. The trip's total is calculated from its bookings, so it cannot be set directly, and the reporting-exclusion flag is paired with a required reason and is set in the app so an excluded trip is never an unexplained gap in a total.
Policies and Activities are created through this same endpoint — they are not separate record types with their own URLs. Pass kind:
kind | Primary date | Secondary date | internal_ref is labelled |
|---|---|---|---|
trip (default) | start_date | end_date | Confirmation # |
policy | effective_at | expiry_at | Policy # |
activity | activity_at | — | Confirmation # |
So a travel insurance policy is:
curl -X POST https://journeyfuse.com/api/v1/trips \
-H "Authorization: Bearer jf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"kind": "policy",
"name": "Rivera - Allianz OneTrip Prime",
"client_id": "a1b2c3d4-...",
"effective_at": "2026-09-20",
"expiry_at": "2026-09-28",
"internal_ref": "POL-4471"
}'
and an excursion is the same call with "kind": "activity" and an activity_at.
Endpoint: POST /api/v1/intake — also reachable at POST /api/v1/bookings
Most booking automations need the same two things: find or create the client, then create a trip for them. Doing that with the endpoints above takes three calls (GET /api/v1/clients?email=…, then POST /api/v1/clients if nothing came back, then POST /api/v1/trips), plus branching logic in Make.com or Zapier to handle the "already a client" case.
This endpoint does all of it in one request. Send the person's name and email, and JourneyFuse either reuses the client you already have or creates a new one, then creates a trip linked to them.
This does not create booking components. Despite the older
/api/v1/bookingspath, this endpoint creates a client and a trip — not hotels, flights, cruises, or any other booking line item on a trip. There is no API for adding booking components yet; those are added in the app./api/v1/intakeis the same endpoint under a name that says what it actually does, and it is the one to use in new automations. The/api/v1/bookingspath keeps working exactly as before, so nothing you have already built needs to change.
Body fields:
| Field | Required | Notes |
|---|---|---|
first_name | Yes | |
last_name | Yes | |
email | No | This is what dedupe keys on. Strongly recommended — see below |
phone | No | Only used when a new client is created |
event_name | No | What they booked, e.g. "3-Hour Planning Session". Used in the auto-generated trip name |
start_date | No | Trip start. Full ISO timestamps are fine — see below |
end_date | No | Trip end |
destination | No | Where they're going |
trip_name | No | Overrides the auto-generated trip name |
status | No | Trip status at creation. Defaults to planning |
notes | No | Goes on the trip — the qualification context for what they're asking for |
client_notes | No | Goes on the client — the standing note about the person, added to their Notes |
source | No | Where the booking came from. Defaults to "Calendly". Stored on the trip and echoed back in the response |
Everything listed under the rest of the client record and the rest of the trip record is accepted here too, so a single intake call can carry the whole form: date of birth and passport details onto the client, trip type, departure city, group size and budget onto the trip.
Client fields are only used when a new client is created. On a dedupe match the existing record is left exactly as it is, so a repeat booker's details are never overwritten by whatever your form happened to collect this time.
email, JourneyFuse looks for an existing client with that email in your workspace. The match is case-insensitive, so Sam@Example.com finds sam@example.com. If more than one matches, the oldest one wins.email out, there is nothing to dedupe on and a new client is created every time. Send the email whenever your source system has one.The response tells you which happened: client_created is true for a brand-new client and false when an existing one was reused.
trip_name, it's composed from what you did send, joined with em dashes: Jamie Rivera — 3-Hour Planning Session — Sep 20, 2026. Parts you leave out are skipped, so a request with just a name produces a trip named Jamie Rivera.start_date and end_date accept a plain date (2026-09-20) or a full ISO timestamp (2026-09-20T14:00:00.000000Z), which is what Calendly sends. Either way only the date is stored, so you can map Calendly's field straight across without reformatting it.status. If your form already tells you the trip is a lead, or a deposit has been taken, send it and skip the correction.source is stored on the trip and shown as a chip on the trip's header, so an advisor can see at a glance that a trip came from Calendly, your website form, or wherever else you send it from. It's also echoed back in the response so your automation can branch on it. Underscores are tidied up for display, so website_form reads as "Web Form" — the same treatment a lead's source gets.Example request:
curl -X POST https://journeyfuse.com/api/v1/intake \
-H "Authorization: Bearer jf_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Jamie",
"last_name": "Rivera",
"email": "jamie@example.com",
"phone": "+1 555 123 4567",
"event_name": "3-Hour Planning Session",
"start_date": "2026-09-20T14:00:00.000000Z",
"end_date": "2026-09-28",
"destination": "Maui",
"source": "Calendly"
}'
Successful response (201 Created):
{
"data": {
"client": {
"id": "a1b2c3d4-...",
"first_name": "Jamie",
"last_name": "Rivera",
"email": "jamie@example.com",
"phone": "+1 555 123 4567",
"household_id": "b2c3d4e5-..."
},
"trip": {
"id": "c3d4e5f6-...",
"name": "Jamie Rivera — 3-Hour Planning Session — Sep 20, 2026",
"client_id": "a1b2c3d4-...",
"start_date": "2026-09-20",
"end_date": "2026-09-28",
"destination": "Maui",
"status": "planning",
"created_at": "2026-07-27T14:30:00.000Z"
},
"client_created": true,
"source": "Calendly"
}
}
Both client.created and trip.created webhooks fire — except that client.created is skipped when an existing client was reused, since no client was created.
Same shape as the Facebook flow above: Calendly → Watch Events, then HTTP → Make a request pointed at https://journeyfuse.com/api/v1/intake with your Authorization header and this body:
{
"first_name": "{{invitee_first_name}}",
"last_name": "{{invitee_last_name}}",
"email": "{{invitee_email}}",
"event_name": "{{event_type_name}}",
"start_date": "{{event_start_time}}",
"source": "Calendly"
}
Book a test appointment on your own Calendly link, then check Trips in JourneyFuse for the new trip before turning the scenario on.
GET /api/v1/suppliers returns every supplier card in your workspace in one
response, built for a script that keeps a copy somewhere else, such as a
nightly sync into Notion or a spreadsheet.
curl https://journeyfuse.com/api/v1/suppliers \
-H "Authorization: Bearer jf_live_your_key_here"
{
"data": [
{
"id": "f02a58d3-d305-4dd2-9a8b-d656162017c9",
"name": "Abercrombie & Kent",
"category": "tours",
"category_label": "Tours",
"is_hidden": false,
"is_preferred": false,
"description": "Luxury travel and guided tours",
"booking_notes": "Book through the agent portal...",
"commission_rate": 14,
"commission_rate_max": null,
"commission_variable": false,
"commission_na": false,
"website": "https://www.abercrombiekent.com",
"agent_portal": null,
"logo_url": "https://...",
"created_at": "2026-03-11T18:10:22Z",
"updated_at": "2026-09-25T12:55:11Z"
}
],
"count": 1
}
| Field | What it holds |
|---|---|
id | The card's permanent ID. It stays the same when a card is renamed, so match on this rather than the name |
is_hidden | true for cards hidden from advisors. Hidden cards are included |
description | The card's About text |
booking_notes | The card's Booking notes |
commission_rate | The rate as a percentage (14 means 14%). commission_rate_max is the top of a range when the card has one |
commission_variable / commission_na | The rate varies by booking / the supplier pays no commission |
updated_at | The last time anything this card shows was edited, including shared supplier details it takes from JourneyFuse |
Agency-wide keys only. Commission rates and Booking notes often hold
account numbers and agency terms, so an Advisor only key gets a 403. Keep
the key on your own server or script, never in a web page.
Looking for a public list for your website? Use
/api/v1/directory/suppliers instead. It needs no key and returns only visible
cards and public details, with no rates or Booking notes.
Errors come back as JSON with a consistent shape:
{ "error": "invalid_input", "message": "first_name and last_name are required (or send name / full_name).", "fields": { "first_name": "required", "last_name": "required" } }
| Status | What it means |
|---|---|
400 | Missing or invalid fields, or the body was not valid JSON |
401 | Missing key, or the key is wrong or revoked |
403 | The key is read-only and the request would change data; the key is Advisor only and the endpoint needs an agency-wide key (the supplier directory); or your agency has turned off advisor API keys |
429 | Too many requests for this key. The limit is 600 per minute per key; wait the number of seconds in the Retry-After header and try again |
404 | The record was not found in your workspace |
500 | Something went wrong on our end |
/api/v1/me first so you know the key works before building the rest of the automation/api/v1/intake when a booking should produce both a client and a trip — it dedupes the client for you, so repeat customers don't pile up as duplicates. Remember it creates a client and a trip, not booking components like hotels or flights.source on leads (like "Facebook Lead Ads") so you can tell at a glance where each one came fromPATCH when details arrive late rather than holding the record back until you have everything. Create it with what the first form gave you, then fill in passport details when they come.This is part of JourneyFuse’s travel agency software: team roles, permissions and agency-wide settings.
Connect JourneyFuse to 6,000+ apps with the JourneyFuse Zapier app: start Zaps from new leads, bookings, payments and proposals, and create clients, leads and trips from other tools.
Automatically send real-time event data to external tools like Zapier, Make.com, or your own API whenever something happens in JourneyFuse.
Ask Claude or ChatGPT about your agency's JourneyFuse data: advisor status, lead assignments, commissions, group requests and suppliers. Read only, signed in as yourself, and never any client details.