{
  "info": {
    "name": "premote API — User & Trip Creation Workflow",
    "_postman_id": "9b1f3a40-7c2e-4e1a-9d8f-premote000001",
    "description": "End-to-end workflow against the premote public API (https://api.premote.io/v1).\n\nFlow:\n1. **Get token** with API client credentials (OAuth 2.0 client_credentials).\n2. **Find** an existing user by email (GET /users/list).\n3. If not found, **create** the user (POST /users).\n4. **List profile fields** for that user (GET /user-fields/profile/:userId).\n5. **Check completeness** to see which fields still need values (POST /trips/completeness/:userId).\n6. **Save** the user's profile data (POST /users/:id/data).\n7. **List trip fields** for the trip type (GET /trip-fields).\n8. **Check completeness again**, then **create the trip(s)** (POST /trips/multiple) with travelerIds = [userId].\n9. **Fetch the result** (GET /trips/:id/object).\n\nThe collection authenticates with a Bearer token at the collection level ({{accessToken}}). Run the requests top-to-bottom (or via the Collection Runner) — test scripts chain the values (accessToken, userId, tripId) automatically.\n\nBefore running, set the `clientId`, `clientSecret`, and `travelerEmail` collection variables.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{accessToken}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.premote.io/v1",
      "type": "string"
    },
    {
      "key": "clientId",
      "value": "prm_live_xxxxxxxx",
      "type": "string",
      "description": "API client_id — generate in Settings → API access."
    },
    {
      "key": "clientSecret",
      "value": "prm_secret_xxxxxxxx",
      "type": "string",
      "description": "API client_secret — shown only once when generated. Treat like a password."
    },
    {
      "key": "accessToken",
      "value": "",
      "type": "string",
      "description": "Set automatically by '1. Get token with API credentials'."
    },
    {
      "key": "travelerEmail",
      "value": "traveler@example.com",
      "type": "string",
      "description": "Email of the user to find or create."
    },
    {
      "key": "travelerFirstName",
      "value": "Jane",
      "type": "string"
    },
    {
      "key": "travelerLastName",
      "value": "Doe",
      "type": "string"
    },
    {
      "key": "userId",
      "value": "",
      "type": "string",
      "description": "Set automatically by the find/create user requests."
    },
    {
      "key": "tripType",
      "value": "BUSINESS_TRIP",
      "type": "string",
      "description": "One of: BUSINESS_TRIP, WORKATION, ASSIGNMENT."
    },
    {
      "key": "tripId",
      "value": "",
      "type": "string",
      "description": "Set automatically by '8. Create trip(s)'."
    }
  ],
  "item": [
    {
      "name": "1. Get token with API credentials",
      "request": {
        "auth": {
          "type": "noauth"
        },
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"client_id\": \"{{clientId}}\",\n  \"client_secret\": \"{{clientSecret}}\"\n}"
        },
        "url": {
          "raw": "{{baseUrl}}/auth/token",
          "host": ["{{baseUrl}}"],
          "path": ["auth", "token"]
        },
        "description": "Exchange API client_id / client_secret for a short-lived bearer token. Public endpoint (no auth). Returns { access_token }, stored into {{accessToken}} for every subsequent request."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('Token issued', function () {",
              "  pm.response.to.have.status(201);",
              "  const json = pm.response.json();",
              "  pm.expect(json.access_token, 'access_token in response').to.be.a('string').and.not.empty;",
              "  pm.collectionVariables.set('accessToken', json.access_token);",
              "  console.log('Saved accessToken');",
              "});"
            ]
          }
        }
      ]
    },
    {
      "name": "2. Find existing user (list users)",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/users/list?blockedFilter=false",
          "host": ["{{baseUrl}}"],
          "path": ["users", "list"],
          "query": [
            {
              "key": "blockedFilter",
              "value": "false",
              "description": "Set to 'true' to include blocked users."
            }
          ]
        },
        "description": "Returns the company user list. The test script searches for {{travelerEmail}} and, if found, sets {{userId}} so the create step can be skipped."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('User list returned', function () {",
              "  pm.response.to.have.status(200);",
              "});",
              "",
              "const email = (pm.collectionVariables.get('travelerEmail') || '').toLowerCase();",
              "let users = pm.response.json();",
              "if (users && !Array.isArray(users) && Array.isArray(users.data)) { users = users.data; }",
              "const match = Array.isArray(users)",
              "  ? users.find(u => (u.email || '').toLowerCase() === email)",
              "  : null;",
              "",
              "if (match && match.id != null) {",
              "  pm.collectionVariables.set('userId', String(match.id));",
              "  console.log('Found existing user id=' + match.id + ' — skip request 3.');",
              "} else {",
              "  pm.collectionVariables.set('userId', '');",
              "  console.log('No user with email ' + email + ' — run request 3 to create one.');",
              "}"
            ]
          }
        }
      ]
    },
    {
      "name": "3. Create user (only if not found)",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"email\": \"{{travelerEmail}}\",\n  \"firstName\": \"{{travelerFirstName}}\",\n  \"lastName\": \"{{travelerLastName}}\"\n}"
        },
        "url": {
          "raw": "{{baseUrl}}/users",
          "host": ["{{baseUrl}}"],
          "path": ["users"]
        },
        "description": "Creates a new user in the current company. `email` is required; `firstName`/`lastName` recommended. If no `roleId` is supplied, the company default role is used. Returns the created user — the test script stores its id into {{userId}}.\n\nNote: returns 409 if a user with this email already exists in the company. The pre-request script skips the call entirely when request 2 already resolved a {{userId}}."
      },
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "const existing = pm.collectionVariables.get('userId');",
              "if (existing) {",
              "  console.log('userId already set (' + existing + ') — skipping create.');",
              "  // Skip this request when running via the Collection Runner.",
              "  if (typeof pm.execution !== 'undefined' && pm.execution.setNextRequest) {",
              "    pm.execution.setNextRequest('4. List profile fields for user');",
              "  }",
              "}"
            ]
          }
        },
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "if (pm.collectionVariables.get('userId')) { return; }",
              "pm.test('User created', function () {",
              "  pm.response.to.have.status(201);",
              "  const json = pm.response.json();",
              "  pm.expect(json.id, 'created user id').to.exist;",
              "  pm.collectionVariables.set('userId', String(json.id));",
              "  console.log('Created user id=' + json.id);",
              "});"
            ]
          }
        }
      ]
    },
    {
      "name": "4. List profile fields for user",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/user-fields/profile/{{userId}}",
          "host": ["{{baseUrl}}"],
          "path": ["user-fields", "profile", "{{userId}}"]
        },
        "description": "Returns all available profile (user) fields for this user, grouped by category with country metadata. Use the field `key`s here as the body keys when saving profile data in request 6."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('Profile fields returned', function () {",
              "  pm.response.to.have.status(200);",
              "  pm.expect(pm.response.json()).to.have.property('userFields');",
              "});"
            ]
          }
        }
      ]
    },
    {
      "name": "5. Check profile completeness for user",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"origin\": \"DE\",\n  \"destination\": \"FR\",\n  \"start_date\": \"2026-07-01\",\n  \"end_date\": \"2026-07-05\",\n  \"travel_reason\": \"client_meetings\"\n}"
        },
        "url": {
          "raw": "{{baseUrl}}/trips/completeness/{{userId}}",
          "host": ["{{baseUrl}}"],
          "path": ["trips", "completeness", "{{userId}}"]
        },
        "description": "Given the intended trip values, returns which user/profile fields are still missing or required for that user. Use this to know which fields to fill via request 6 before creating the trip. The body is a key/value map of trip values (snake_case)."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('Completeness returned', function () {",
              "  pm.response.to.have.status(201);",
              "});"
            ]
          }
        }
      ]
    },
    {
      "name": "6. Save user profile data",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"date_of_birth\": \"1990-01-15\",\n  \"nationality\": \"DE\",\n  \"home_address\": \"Hauptstrasse 1, 10115 Berlin, Germany\"\n}"
        },
        "url": {
          "raw": "{{baseUrl}}/users/{{userId}}/data",
          "host": ["{{baseUrl}}"],
          "path": ["users", "{{userId}}", "data"]
        },
        "description": "Saves profile field values for the user. Body is a key/value map where each key is a user-field `key` from request 4 (e.g. date_of_birth, nationality, home_address). Repeat / extend with the fields flagged as missing by request 5."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('Profile data saved', function () {",
              "  pm.expect(pm.response.code).to.be.oneOf([200, 201]);",
              "});"
            ]
          }
        }
      ]
    },
    {
      "name": "7. List trip fields by type",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/trip-fields?tripType={{tripType}}&includeHidden=false",
          "host": ["{{baseUrl}}"],
          "path": ["trip-fields"],
          "query": [
            {
              "key": "tripType",
              "value": "{{tripType}}",
              "description": "BUSINESS_TRIP | WORKATION | ASSIGNMENT"
            },
            {
              "key": "includeHidden",
              "value": "false"
            }
          ]
        },
        "description": "Returns the trip fields relevant to the given trip type, grouped by category. Use these field `key`s in the `values` object of request 8."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('Trip fields returned', function () {",
              "  pm.response.to.have.status(200);",
              "  pm.expect(pm.response.json()).to.have.property('tripFields');",
              "});"
            ]
          }
        }
      ]
    },
    {
      "name": "8. Re-check completeness, then create trip(s)",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"travelerIds\": [{{userId}}],\n  \"type\": \"{{tripType}}\",\n  \"values\": {\n    \"origin\": \"DE\",\n    \"destination\": \"FR\",\n    \"start_date\": \"2026-07-01\",\n    \"end_date\": \"2026-07-05\",\n    \"travel_reason\": \"client_meetings\",\n    \"location_abroad\": \"10 Rue de Rivoli, 75001 Paris, France\"\n  }\n}"
        },
        "url": {
          "raw": "{{baseUrl}}/trips/multiple",
          "host": ["{{baseUrl}}"],
          "path": ["trips", "multiple"]
        },
        "description": "Creates one or more trips. `travelerIds` is a single-item array of the user found/created above. `type` is the trip type; `values` is the EAV trip data keyed by trip-field `key` (snake_case): origin/destination are ISO-3166 alpha-2, start_date/end_date are YYYY-MM-DD, travel_reason is one of client_meetings | attend_colleague_meeting | seminar_attend | seminar_paid | training | software_issues | client_meetings_presentation | other, location_abroad is a geocodable address.\n\nThe pre-request script re-runs the completeness check (POST /trips/completeness/:userId) for visibility before creating. The test script stores the first created trip id into {{tripId}}."
      },
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "// Re-check completeness for the traveler before creating the trip.",
              "const baseUrl = pm.collectionVariables.get('baseUrl');",
              "const userId = pm.collectionVariables.get('userId');",
              "const token = pm.collectionVariables.get('accessToken');",
              "pm.sendRequest({",
              "  url: baseUrl + '/trips/completeness/' + userId,",
              "  method: 'POST',",
              "  header: {",
              "    'Content-Type': 'application/json',",
              "    'Authorization': 'Bearer ' + token",
              "  },",
              "  body: {",
              "    mode: 'raw',",
              "    raw: JSON.stringify({",
              "      origin: 'DE', destination: 'FR',",
              "      start_date: '2026-07-01', end_date: '2026-07-05',",
              "      travel_reason: 'client_meetings'",
              "    })",
              "  }",
              "}, function (err, res) {",
              "  if (err) { console.log('Completeness pre-check error', err); }",
              "  else { console.log('Completeness pre-check status', res.code); }",
              "});"
            ]
          }
        },
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('Trip(s) created', function () {",
              "  pm.response.to.have.status(201);",
              "  let json = pm.response.json();",
              "  const trip = Array.isArray(json) ? json[0] : (json.trips ? json.trips[0] : json);",
              "  if (trip && trip.id != null) {",
              "    pm.collectionVariables.set('tripId', String(trip.id));",
              "    console.log('Created trip id=' + trip.id);",
              "  }",
              "});"
            ]
          }
        }
      ]
    },
    {
      "name": "9. Fetch trip object (the reply)",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/trips/{{tripId}}/object",
          "host": ["{{baseUrl}}"],
          "path": ["trips", "{{tripId}}", "object"]
        },
        "description": "Returns the full, serialized trip object (flattened EAV values, dates normalized) for the trip created in request 8."
      },
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "pm.test('Trip object returned', function () {",
              "  pm.response.to.have.status(200);",
              "});"
            ]
          }
        }
      ]
    }
  ]
}
