{
  "info": {
    "name": "Sparro API",
    "description": "Generated from @sparro/api-contracts's src/spec.ts — do not hand-edit. Run `pnpm --filter @sparro/api-contracts build-postman` to regenerate.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Start the authorization-code flow",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=https://example.com/callback&scope=invoices:read customers:read&state=&code_challenge=&code_challenge_method=S256",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "oauth",
            "authorize"
          ],
          "query": [
            {
              "key": "client_id",
              "value": "YOUR_CLIENT_ID",
              "description": "Your registered client ID.",
              "disabled": false
            },
            {
              "key": "redirect_uri",
              "value": "https://example.com/callback",
              "description": "Must match a registered redirect URI.",
              "disabled": false
            },
            {
              "key": "scope",
              "value": "invoices:read customers:read",
              "description": "Space-separated list of requested scopes.",
              "disabled": false
            },
            {
              "key": "state",
              "value": "",
              "description": "Opaque value round-tripped back to your redirect URI. Required for partner apps.",
              "disabled": true
            },
            {
              "key": "code_challenge",
              "value": "",
              "description": "PKCE challenge (S256).",
              "disabled": true
            },
            {
              "key": "code_challenge_method",
              "value": "S256",
              "description": "Always S256 when code_challenge is set.",
              "disabled": true
            }
          ]
        },
        "description": "Redirect the org owner's browser here. PKCE (code_challenge, code_challenge_method=S256) is required for public clients. Only an owner of the company can approve.",
        "auth": {
          "type": "noauth"
        }
      }
    },
    {
      "name": "Exchange a code or refresh token for an access token",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/x-www-form-urlencoded",
            "type": "text"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/oauth/token",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "oauth",
            "token"
          ]
        },
        "description": "application/x-www-form-urlencoded body: grant_type, client_id, client_secret (confidential clients), code, redirect_uri, code_verifier, refresh_token.",
        "auth": {
          "type": "noauth"
        },
        "body": {
          "mode": "urlencoded",
          "urlencoded": [
            {
              "key": "grant_type",
              "value": "authorization_code"
            },
            {
              "key": "client_id",
              "value": "{{clientId}}"
            },
            {
              "key": "client_secret",
              "value": "{{clientSecret}}",
              "description": "Confidential clients only."
            },
            {
              "key": "code",
              "value": ""
            },
            {
              "key": "redirect_uri",
              "value": "{{redirectUri}}"
            },
            {
              "key": "code_verifier",
              "value": "",
              "description": "Required if the authorize request set a code_challenge."
            }
          ]
        }
      }
    },
    {
      "name": "Get the connected company",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/org",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "org"
          ]
        },
        "description": "Requires the org:read scope. The company the token was granted for: id, name, CVR number and creation time."
      }
    },
    {
      "name": "List invoices",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/invoices",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "invoices"
          ]
        },
        "description": "Requires the invoices:read scope."
      }
    },
    {
      "name": "List customers",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/customers",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "customers"
          ]
        },
        "description": "Requires the customers:read scope."
      }
    },
    {
      "name": "List shifts",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/shifts?cursor=&limit=100",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "shifts"
          ],
          "query": [
            {
              "key": "cursor",
              "value": "",
              "description": "The next_cursor of the previous page.",
              "disabled": true
            },
            {
              "key": "limit",
              "value": "100",
              "description": "Max results per page (default 100, max 500).",
              "disabled": true
            }
          ]
        },
        "description": "Requires the shifts:read scope. Planned and completed shifts: start, end, worked hours, and the employee as an opaque employee_ref that is different for every app. No names or addresses. Paginated by cursor."
      }
    },
    {
      "name": "Create a shift",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}",
            "type": "text"
          },
          {
            "key": "Content-Type",
            "value": "application/json",
            "type": "text"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/shifts",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "shifts"
          ]
        },
        "description": "Requires the shifts:write scope and an Idempotency-Key header. Plans one shift for an employee of the connected company. Rest-law rules apply and cannot be overridden; the employee is notified like for any new shift.",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"employee_ref\": \"emp_Qm9hcmRFeGFtcGxlUmVmZXJlbmNlS2V5Rm9yRG9jc09\",\n  \"start_time\": \"2026-11-02T08:00:00+01:00\",\n  \"end_time\": \"2026-11-02T16:00:00+01:00\",\n  \"break_minutes\": 30\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "Register hours",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}",
            "type": "text"
          },
          {
            "key": "Content-Type",
            "value": "application/json",
            "type": "text"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/shifts/hours",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "shifts",
            "hours"
          ]
        },
        "description": "Requires the shifts:write scope and an Idempotency-Key header. Registers hours an employee already worked. The entry is submitted and counts for nothing until the company's owner approves it. Same body as Create a shift, but the times must have already happened.",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"employee_ref\": \"emp_Qm9hcmRFeGFtcGxlUmVmZXJlbmNlS2V5Rm9yRG9jc09\",\n  \"start_time\": \"2026-10-30T08:00:00+01:00\",\n  \"end_time\": \"2026-10-30T16:00:00+01:00\",\n  \"break_minutes\": 30\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    },
    {
      "name": "List mileage",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/mileage?cursor=&limit=100",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "mileage"
          ],
          "query": [
            {
              "key": "cursor",
              "value": "",
              "description": "The next_cursor of the previous page.",
              "disabled": true
            },
            {
              "key": "limit",
              "value": "100",
              "description": "Max results per page (default 100, max 500).",
              "disabled": true
            }
          ]
        },
        "description": "Requires the mileage:read scope. Trips: date and distance in km, and the employee as an opaque employee_ref. No addresses, purpose or amounts. Paginated by cursor."
      }
    },
    {
      "name": "Create a mileage entry",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Idempotency-Key",
            "value": "{{$guid}}",
            "type": "text"
          },
          {
            "key": "Content-Type",
            "value": "application/json",
            "type": "text"
          }
        ],
        "url": {
          "raw": "{{baseUrl}}/mileage",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "mileage"
          ]
        },
        "description": "Requires the mileage:write scope and an Idempotency-Key header. Registers one trip for an employee. The trip waits for the company's owner to approve it; nothing is booked and nothing is paid until then. No addresses or vehicle are accepted.",
        "body": {
          "mode": "raw",
          "raw": "{\n  \"employee_ref\": \"emp_Qm9hcmRFeGFtcGxlUmVmZXJlbmNlS2V5Rm9yRG9jc09\",\n  \"date\": \"2026-10-30\",\n  \"distance_km\": 42.5,\n  \"purpose\": \"Customer visit\"\n}",
          "options": {
            "raw": {
              "language": "json"
            }
          }
        }
      }
    }
  ],
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{accessToken}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://app.sparro.dk/api/v2"
    }
  ]
}
