Changelog
v1 only ever adds — columns, resources, operators. We never remove or repurpose a shipped field within v1; breaking changes would ship as a parallel v2 with ≥6 months of overlap and advance email notice to every token holder.
2026-11-05
One graduation year on students
/v1/studentsand/v1/advisor_studentshave three new read-only columns:graduation_year, the year CareerOS uses everywhere (a university or partner date first, else the end of the student's class cohort, else the student's own),graduation_source, where it comes from, andgraduation_is_official. Filter with?graduation_year=eq.2026.graduation_dateis unchanged: it is still the date stored on the student, which is what you write and read back. It can be empty when the year comes from the cohort.
2026-11-04
Alumni status is re-checked every night
is_alumniis now derived every night for every student, in both directions, from the official graduation date: thegraduation_dateyou send, otherwise their cohort'send_date. Atruewhose date is still ahead is cleared the next night, and a sentis_alumnithat disagrees with the date lasts only until then. With no date on either, the flag you sent stays.
2026-11-04
Writes promote graduates straight away
- A write that sends
graduation_dateorcohort_namewithoutis_alumninow promotes the student straight away when their official graduation date has passed: thegraduation_dateyou send, otherwise their cohort'send_date. It no longer waits for the student to sign in. - That derivation only promotes for now. To clear the flag, send
is_alumni: false.
2026-10-20
Appointments record whether they took place
/v1/appointmentshas a newstatusvalue,took_place: the advisor marked the meeting as held, or it was stillconfirmed7 days after it ended. A past meeting therefore moves fromconfirmedtotook_place(orno_show), so filterstatus=in.(confirmed,took_place)where you counted held meetings asconfirmedbefore.- The move made 7 days after the end leaves
updated_atas it was; a mark by the advisor updates it.
2026-10-09
Your record wins over an address nobody has confirmed
- A student write takes over an address that another user only asked to add. A CareerOS user can ask to add a second address to their account, and until they confirm it from that mailbox it is held for them but proves nothing about who owns it. A write carrying such an address used to update the user who asked for it. It now cancels that request and gives the address to your student, creating or updating them as it would for a free address, and the row's
notessay so without naming the other user. If yourexternal_idpoints at the user who made the request, that user is updated as before. - Nothing else changes: an address a CareerOS account has confirmed still identifies that account, with the same checks and rejection codes as before.
2026-09-12
Correct one name at a time, and see an `external_id` re-key
PATCHnow acceptsfirst_nameandlast_nameindependently. Sending one without the other was refused withMISSING_NAME, so correcting a single name — the most common reason toPATCHat all — meant resending both. Each is now judged on its own: omit one to leave it alone, send one to change it. A name you *do* send still may not be empty.- Changing a student's
external_idis no longer silent. Pushing a student by email with a differentexternal_idre-keys them — the new value is stored and the old one stops matching. That was already true, but the row came backunchangedwith nothing said. It now reportsupdatedand carries a note naming both the old and new value, so a re-key can't slip past a sync that only logs changes.
2026-09-12
`cohort_display_name` is dropped, and `dropped_fields` is a closed list
cohort_display_nameon a student is no longer applied. It only ever did anything when a student write could create a cohort, which it no longer can, so the value was being accepted and silently discarded. It is now reported indropped_fieldslike any other field we do not store. Set a cohort's label withdisplay_nameonPOST/PATCH /v1/cohorts.dropped_fieldsis a closed list of eleven names, not "anything we don't recognise":birth_date,person_birth_date,gender,person_gender,grades,courses,top_courses,degree,specialization,anticipated_completion_dateandcohort_display_name. Any other unrecognised field refuses the whole request withINVALID_BODY. The reference previously listed only seven of the eleven, which read as though the Populi-style spellingsperson_birth_dateandperson_genderwould be refused — they are not.
2026-09-12
A student is identity plus a cohort
cohort_namemoves onto the student, and theprogramsarray is gone. Placement used to be buried insideprograms[].cohort_name; it is now a plain field on the student, exactly like the column in the roster file your advisors upload.graduation_datemoves with it. Everything else the array carried — per-programme name, status, course year — is no longer stored, so send a student as identity, cohort and flags and nothing more.POSTandPUTnow require a complete object. A cohort needsname,display_name,program,degree,start_date,end_date,intake_yearandintake_term; a student needsemail,first_name,last_name,cohort_nameandis_alumni. These are precisely the columns the advisor roster template makes mandatory — only campus and study mode are optional there, and the same two are optional here. A half-populated cohort is invisible to the filters advisors work from, which is why it is no longer accepted.- A student write no longer creates cohorts. Naming a cohort that does not exist is refused with
COHORT_NOT_FOUNDinstead of quietly creating an empty one, because a student write carries no degree, dates or intake to build it from. Push/v1/cohortsfirst — which the guide has always recommended and the API now requires. PATCHis unchanged in spirit and more useful in practice. It never creates, so everything except the identity may be omitted, and an omitted field is left exactly as it is rather than cleared. Sendemailplus only what you want to change: correct a name, add a phone number, setis_alumni, or move someone to a different cohort withcohort_name. To move students into a cohort that does not exist yet, create it withPOST /v1/cohortsand then patch them. A cohort'snameremains frozen — every other cohort field is editable.- The API reference now has separate create and update schemas —
CohortWrite/CohortPatchandStudentWrite/StudentPatch— so it states exactly which fields a create requires rather than showing one schema for every verb.
2026-09-11
Every student must be placed in a cohort
- A student write whose programmes carry no
cohort_nameis now refused withMISSING_COHORT, and nothing is written for that row. Until now such a student was created withcohort_id: nulland reported as a plain success — an account on no advisor's roster, inheriting no résumé templates and receiving nothing targeted at their year. Only the programme the student should be placed in needs a name; the others are recorded as history either way. The most common cause is a source row with no graduation year, since a cohort name is usually built from it. - When several programmes are sent, placement now ranks only the programmes that carry a
cohort_name. Previously an in-progress programme without one would win the ranking and leave the student unplaced even though another programme named a cohort; now the student is placed in that cohort instead. The programme placement picks is also the one whosegraduation_dateis stored, so the date and the cohort always come from the same programme.
2026-09-07
Write students into CareerOS
/v1/studentsnow acceptsPOST(create),PUT(idempotent upsert) andPATCH(update). Each takes a batch of up to 500 students and returns per-row results, so one refused student never blocks the rest of a sync. Writes require a token CareerOS has enabled for writing, scoped to a single university.- Address students by email. It is unique across CareerOS, so a nightly sync is idempotent without you storing an identifier of ours. Send
external_idas well if you have a durable student number: it then takes precedence when matching, so a student whose address changes stays one account instead of becoming two. - Cohorts are writable too:
POST,PUTandPATCH /v1/cohortsmanage display name, programme, degree, campus, intake year and term, dates, study mode and résumé templates — the same fields your advisors edit in the app, so you never need to send a roster spreadsheet. A cohort is addressed bynamewithin your university andnameis frozen once created. Omitting a field leaves it untouched; sendingnullclears it. Assigning a student to a cohort is part of the student write: sendcohort_nameon the programme. - Academic context lives on the cohort, not on every student: set
degreeonce on the cohort and students placed there inherit it, and expected completion is the cohort'send_dateplusgraduation_yearon the programme. A student's owngraduation_datemeans they have actually graduated. - A created student is an invited account: no login identity until the person first signs in, and no email is sent.
invitations_sentis always0— your advisors decide when a student is contacted. Requests are student-grain, so an extract with one row per course per term must be grouped first; send the programmes in aprogramsarray. - New writable fields beyond name and graduation date: contact
phone(E.164 with country code — a local number is refused rather than guessed), and per-programmeprogram,course_year,statusand cohort placement. Sendgraduation_dateonly once a person has genuinely graduated: a past date is what makes an account an alumni account. is_alumniis writable. CareerOS normally derives it fromgraduation_date, but only when the student next signs in or their cohort or graduation date changes — there is no nightly job, so a historical roster you push would readis_alumni: falseuntil each person logged in. Sendis_alumnion the student to set it straight away. Omitting the field is not the same as sendingfalse: omitting leaves the derivation alone. The derivation still runs later and remains the authority, so a value that disagrees withgraduation_dateis overwritten the next time that student's cohort or graduation date changes — when it disagrees, we say so in that row'snotesrather than let you assume it stuck.- Cohort placement picks the programme in progress first, then one not yet started, then a finished one — a future exchange never displaces the programme a student is in today. Naming a
cohort_namethat does not exist creates it with the programme metadata you sent. PATCH /v1/students?id=eq.is unchanged. The original single-row form still takes a flat body offirst_name/last_name/graduation_dateand still answers204. If you already integrate against it, nothing about your calls needs to change; the batch form is what you get when you omit theidfilter.- Not stored on a student: dates of birth and gender (we hold no demographics), and
degree,specializationandanticipated_completion_date—degreebelongs on the cohort, and expected completion is the cohort's end date. Courses and grades are not stored yet; there is no transcript model today. Send any of them anyway and they are dropped, with the field names echoed back indropped_fieldsso you can see what was ignored rather than assume it was saved. DELETEreturns405. Erasure runs through a staged deletion flow with a grace window and its own audit trail, so it is not something an API token can start — email support@careeros.com.- An email that already belongs to an account at another university, to a non-student account, or to an existing CareerOS user with no university (self-serve) is refused rather than adopted — and so is a write addressed by
external_idwhose student has since moved to another university or become staff.updated_aton/v1/studentsnow moves whenever a student changes, so incremental syncs that page on it no longer miss edits. - Contact email stays create-only. Pushing a student by
external_idwith a new address records it as a secondary address and tells you so in that row'snotes; it is a real sign-in address from the moment it is written, but it does not become the contact address. Email support@careeros.com to complete a domain migration. - Two things that used to pass silently now come back as rejections: a
cohort_namethat matches more than one of your cohorts (COHORT_NAME_AMBIGUOUS, so we never guess which), and a résumé-template name that matches none of yours (UNKNOWN_TEMPLATE).
2026-08-12
Event participants and advisor appointments
- New resource:
/v1/appointments— every booking of your advisors' CalendarOS appointment types, across all advisors: advisor, student, appointment type, timing,status(requested/confirmed/rejected/no_show/cancelled), and location. Full history — past and upcoming, all statuses, including bookings of appointment types that were since deleted. Free-text booking notes and booking-form answers are not exposed. - New resource:
/v1/event_participants— one row per student per event: who registered for each event and who actually attended. Filter by event with?event_id=eq.; each row carries the student's identity, cohort, rawstatus/attendance_status, and derivedis_registered/has_attended/is_waitlistedbooleans that sum exactly to the counts on/v1/events. - Cancelled registrations are included with
is_registered=false, so sign-up drop-off is measurable.attendance_status=pendingmeans attendance was never marked — check the event'sattendance_trackedbefore reading absence into it.
2026-08-11
Test accounts removed; deactivated staff and students retained
/v1/eventsand/v1/alumnino longer return records that originate from CareerOS internal test accounts. Expect a small drop in row counts — these were never real events or real alumni, so any figure they affected was overstated./v1/advisor_students: theassigned_advisor_*columns now include advisors who have since been suspended or moved to another role. Previously an advisor disappeared from these columns the moment their account was deactivated, which made some students appear to have no advisor at all and undercountedassigned_advisor_countfor others. Historic advising relationships are facts and now stay reported./v1/events:registrations,attendedandwaitlistednow include students who have since been deactivated or have left their cohort. A student who attended an event keeps that attendance in your numbers. Counts can only go up.- No columns were added, removed or renamed, and no filters changed. If you snapshot these resources, expect a one-off step in the affected counts rather than a gradual shift.
2026-08-10
Analytics resources: engagement, companies, events
- New resource:
/v1/advisor_students— the student roster with advising context: applicationstatus,profile_completeness,onboarding_updated_at, cohort label, and assigned advisors. Same students as/v1/students, more columns. - New resource:
/v1/student_engagement— per-student totals for companies saved, contacts saved, outreach sent, replies received, applications submitted, interviews obtained, referrals and coffee chats. Students with no activity are included with zeros so denominators stay correct. - New resource:
/v1/student_engagement_monthly— the same metrics at one row per student per active month, for time-series reporting. - New resource:
/v1/companies— companies your students saved or applied to, with save counts and an application-funnel breakdown, one row per company per university. - New resource:
/v1/events— events your university organises, with registration and attendance counts. Useattendance_trackedto exclude events where attendance was never recorded before computing rates. cohort_namenow means the same thing everywhere./v1/studentsand/v1/resumesreturned a cohort's internalname; they now return its display label — thedisplay_nameyour advisors set, falling back tonamewhen there isn't one. That is what the app shows and what the new resources above already returned.cohort_idis unchanged, so anything keyed on the id is unaffected.- All five use the metric definitions of the equivalent CareerOS advisor screens. Two known differences from the in-app figures are documented per resource: ghost and deleted accounts are excluded here, and
student_engagement.company_savedcounts the auto-assigned CareerOS company while/v1/companiesexcludes it.
2026-07-23
Resume source files move to private storage
/v1/resumes:source_urlnow always returnsnull. Student-uploaded source PDFs moved to private storage as part of a platform-wide PII review; the raw storage URL is no longer usable. The column stays in the response shape. It was populated on almost no rows — reach out if your integration relied on it.
2026-07-15
Profiles, onboarding, alumni — and a cleaner resumes shape
- New resource:
/v1/student_profiles— structured career profiles (summary + sections), respecting student visibility settings. - New resource:
/v1/onboarding— each student's latest onboarding questionnaire; sensitive demographic answers are removed server-side. - New resource:
/v1/alumni— the alumni directory with current position where known. /v1/studentsgainedcohort_idandcohort_namecolumns./v1/resumes: removed the legacybodyandpreview_urlcolumns —contentis the single canonical resume payload.
2026-07-14
Jobs
- New resource:
/v1/jobs— employer-posted, advisor-curated, and aggregated postings from the last 365 days, withuniversity_ids/cohort_ids/all_cohortsvisibility columns.
2026-05-19
Launch
- Partners API v1 goes live:
/v1/universities,/v1/students(GET + PATCH),/v1/cohorts,/v1/campuses,/v1/resumes. - Consortium tokens: one token can span every member university, resolved at request time.