Submit Self Timesheet
This endpoint submits a timesheet for a user, sets every timecard to pending, and emails the people who review or follow that time.
Endpoint
Section titled “Endpoint”POST /v1/timesheets/timesheets/submit-selfAuthentication
Section titled “Authentication”Include your API key in the x-api-key header. See Authentication.
Required Scope
Section titled “Required Scope”timesheets:submit-selfRequest Body
Section titled “Request Body”The request body is not validated against a schema. Send every field marked Required in the shape shown. A missing or malformed field usually causes a 500, not a 400.
| Field | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | User whose timesheet is submitted. Accepts an external user ID (user-a) or an internal one (tenant-123#user-a). It can be any user in your account. |
timecards | object[] | Yes | Timecards in the timesheet. Send at least one. |
Each timecard:
| Field | Type | Required | Description |
|---|---|---|---|
projectId | string | Yes | Project ID. |
taskId | string | Yes | Task ID. Each projectId and taskId pair identifies one timecard. |
data | object | Yes | One entry for each day: Saturday, Sunday, Monday, Tuesday, Wednesday, Thursday, and Friday. All seven keys are required. |
Each day in data:
| Field | Type | Required | Description |
|---|---|---|---|
date | string | Yes | Date in YYYY-MM-DD format. |
hours | number | Yes | Hours worked on that day. |
description | string | null | No | Description of the work. |
The API takes the timesheet period from the earliest and latest date in the first timecard, and builds the timesheet ID from that period. Use the same seven dates in every timecard. The API does not check that each date falls on the weekday of its key.
Example Request Body
Section titled “Example Request Body”{ "userId": "user-a", "timecards": [ { "projectId": "project-mno123pqr4567890", "taskId": "task-pqr123stu4567890", "data": { "Saturday": { "date": "2022-12-31", "hours": 0, "description": null }, "Sunday": { "date": "2023-01-01", "hours": 0, "description": null }, "Monday": { "date": "2023-01-02", "hours": 8, "description": "Worked on implementation" }, "Tuesday": { "date": "2023-01-03", "hours": 8, "description": "Worked on implementation" }, "Wednesday": { "date": "2023-01-04", "hours": 8, "description": "Worked on implementation" }, "Thursday": { "date": "2023-01-05", "hours": 8, "description": "Worked on implementation" }, "Friday": { "date": "2023-01-06", "hours": 8, "description": "Worked on implementation" } } } ]}Example Request
Section titled “Example Request”curl -X POST "https://api.crewsum.com/v1/timesheets/timesheets/submit-self" \ -H "x-api-key: $CREWSUM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "userId": "user-a", "timecards": [ { "projectId": "project-mno123pqr4567890", "taskId": "task-pqr123stu4567890", "data": { "Saturday": { "date": "2022-12-31", "hours": 0, "description": null }, "Sunday": { "date": "2023-01-01", "hours": 0, "description": null }, "Monday": { "date": "2023-01-02", "hours": 8, "description": "Worked on implementation" }, "Tuesday": { "date": "2023-01-03", "hours": 8, "description": "Worked on implementation" }, "Wednesday": { "date": "2023-01-04", "hours": 8, "description": "Worked on implementation" }, "Thursday": { "date": "2023-01-05", "hours": 8, "description": "Worked on implementation" }, "Friday": { "date": "2023-01-06", "hours": 8, "description": "Worked on implementation" } } } ] }'const response = await fetch( "https://api.crewsum.com/v1/timesheets/timesheets/submit-self", { method: "POST", headers: { "x-api-key": process.env.CREWSUM_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ userId: "user-a", timecards: [ { projectId: "project-mno123pqr4567890", taskId: "task-pqr123stu4567890", data: { Saturday: { date: "2022-12-31", hours: 0, description: null }, Sunday: { date: "2023-01-01", hours: 0, description: null }, Monday: { date: "2023-01-02", hours: 8, description: "Worked on implementation" }, Tuesday: { date: "2023-01-03", hours: 8, description: "Worked on implementation" }, Wednesday: { date: "2023-01-04", hours: 8, description: "Worked on implementation" }, Thursday: { date: "2023-01-05", hours: 8, description: "Worked on implementation" }, Friday: { date: "2023-01-06", hours: 8, description: "Worked on implementation" }, }, }, ], }), },);const result = await response.json();POST /v1/timesheets/timesheets/submit-self HTTP/1.1Host: api.crewsum.comx-api-key: <your-api-key>Content-Type: application/json
{ "userId": "user-a", "timecards": [ { "projectId": "project-mno123pqr4567890", "taskId": "task-pqr123stu4567890", "data": { "Saturday": { "date": "2022-12-31", "hours": 0, "description": null }, "Sunday": { "date": "2023-01-01", "hours": 0, "description": null }, "Monday": { "date": "2023-01-02", "hours": 8, "description": "Worked on implementation" }, "Tuesday": { "date": "2023-01-03", "hours": 8, "description": "Worked on implementation" }, "Wednesday": { "date": "2023-01-04", "hours": 8, "description": "Worked on implementation" }, "Thursday": { "date": "2023-01-05", "hours": 8, "description": "Worked on implementation" }, "Friday": { "date": "2023-01-06", "hours": 8, "description": "Worked on implementation" } } } ]}Response
Section titled “Response”On success, the API returns HTTP 200 OK.
Example Response
Section titled “Example Response”data has one item for each email the API tried to send. status is fulfilled or rejected. A rejected item also has a rejectionReason. The response does not include the timesheet ID. It is <tenantId>#<userId>#<periodStart>_<periodEnd>, here tenant-123#user-a#20221231_20230106.
{ "message": "Successfully submitted timesheet", "data": [ { "to": "manager@example.com", "status": "fulfilled" } ]}Behavior
Section titled “Behavior”- New timesheet: the API creates the timesheet, sets its
submittedAt, and stores every timecard aspending. - Existing timesheet: if a timesheet with the same ID exists, the API merges the request into it. Timecards that are new in the request are added, timecards left out of the request are removed, and changed timecards are updated. Then every timecard is set back to
pending, including timecards that were already approved.submittedAtis set to the current time andresubmissionAllowedis set tofalse. - A submitted timesheet can be submitted again only if resubmission was allowed in Crewsum or at least one timecard is
rejected. Otherwise the API returns400. A saved draft can always be submitted. - The API returns
400if the hours on any one date add up to more than 24 across all timecards. - The API emails:
- project contacts whose notification level for the project is copy only or copy with link (copy-with-link contacts get a link to approve the time),
- the user,
- the user’s supplier contacts,
- your internal copy address, if it is turned on.
- The response lists the email addresses that were sent to.
- After the timesheet is submitted, Crewsum sends the
timesheet.submittedwebhook event. The payload is{ "timesheet": <timesheet> }, in the same shape as Get Timesheet. - The timesheet is saved before the emails are sent. If the email step fails, the API returns
500, but the submission is kept and no webhook is sent.
Error Responses
Section titled “Error Responses”| Status | Meaning |
|---|---|
400 | userId is missing. Message: UserID Missing. |
400 | One date has more than 24 hours in total. Message: Error submitting timesheet. Daily hours cannot exceed 24 hours on <date>. |
400 | The timesheet was already submitted and cannot be submitted again. Message: Error submitting timesheet. Timesheet already submitted! |
500 | timecards is empty, or a date in the first timecard is not valid. Message: Error parsing timesheet. |
500 | The user could not be found or loaded. Message: Error fetching startup information for the submission. |
500 | Crewsum could not save the timesheet. The message starts with Error saving timesheet. or Error submitting timesheet. and names the step that failed. |
500 | The timesheet was saved, but the emails could not be prepared. Message: Error sending submission notifications. |
500 | The body is missing or has the wrong shape, for example no timecards array. The response has a generic error body. |
Authentication and scope failures are described in Errors.