Endpoints — Site
Every call in this family: what the card records, the edge cases that live on the call, and the verdict of every entry that measured it. Each of those lines names the door holding that entry's rules. The map is corpus/INDEX.md.
GET /license_info
Seat counts in a {data, status} envelope, not the JSON:API one. rule decides whether free is a number or -1 for unlimited, and none of the three counts equals the HumanUser row count.
The envelope is
{"data": ..., "status": "success"}. There is nolinksand nolinks.self, so a client that reads every response through a JSON:API decoder fails on this one.The site's own
/spec.jsonexample gives the five values as strings ("assigned": "56"). The site returns integers. Type-check rather than trusting the example.freeis-1, not0ornull, whenruleisunlimited. Subtracting it fromtotalgives a number larger than the licence.assignedis not the HumanUser row count and not the size of theGET /subscription_seat/user_subscriptionshash. On the probed site the three were 4, 24 and 14. Do not derive one from another./spec.jsonmarks this call with thesudo_as_loginsecurity scope. A script token reads it without one, and so does a token acting as anAdmin. Acting as anArtistit is401code 110Must sudo as Administrator to query license information, the API naming the scope in its own refusal (probe 027).
Measured by
047_site_facts_and_the_working_week(findings) — Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
rules:doors/findings-schema
Silent on this call
047_site_facts_and_the_working_week— Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
corpus/endpoints/get_license_info.md
GET /preferences
The only place the unit behind a duration field is named. prefs narrows it to one key, and hours_per_day and duration_units are the pair a renderer needs.
view_master_settingsandcreative_review_settingsare JSON encoded inside a string, not nested objects. Decode them a second time.hours_per_dayis a float,8.0, not an integer.An unknown
prefsname is not an error. The key is absent fromdataand the status is 200, so test for the key rather than the status.
Measured by
002_schema(findings) — Fetch /schema once for the type list, then /schema//fields only for types you actually need: it is the expensive call (48KB, ~330ms each) and must never be looped over all types.
rules:doors/findings-schema
corpus/endpoints/get_preferences.md
PUT /preferences/update
Enables a custom entity slot and nothing else. On the probed site every body, valid or not, answered 400 code 111 Updating the preferences is not available, so the shape stays unverified.
The success path is deliberately unexercised. Enabling a custom entity slot changes the schema of the whole site for every user and cannot be undone by this endpoint:
/spec.jsonnames nodisable_entity. Only rejections were sent.The 400 precedes validation. An empty body and a complete one get the same status, the same code and the same string, so the error says nothing about whether the body was right. A client cannot use it to test its own payload.
codeis 111 andsourceisnull, where the parameter errors elsewhere in the API report 103 with a populatedsource. Readcode, not the status.The refusal is a 400, not a 403. Whatever gates this call, it is not reported as an authorisation failure, so retrying with different credentials is not indicated by the response.
Enabling a slot is site configuration. Read which slots are already enabled with
PYTHONPATH=src python -m sg_groundtruth.schema entities --customrather than probing for them.
Measured by
047_site_facts_and_the_working_week(findings) — Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
rules:doors/findings-schema
Silent on this call
047_site_facts_and_the_working_week— Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
corpus/endpoints/put_preferences_update.md
GET /schedule/work_day_rules
One row per calendar day, both ends inclusive, no paging at 730 rows. A project_id or user_id that does not exist answers 200 with the studio default instead of an error.
Two error shapes on one endpoint. A missing or out-of-order parameter is a JSON:API
errorsarray; an unparseable date is a bare{"status": "error", "error": "invalid date"}with noerrorskey at all. A client readingr.json()["errors"][0]["title"]raisesKeyErroron the second.A
project_idoruser_idthat is not on the site answers 200 with the studio rule. Nothing in the body says which scope answered, other thanreason, andreasonreadsSTUDIO_WORK_WEEKfor both the fallback and a genuine studio-wide answer. Check the id exists before trusting the schedule.Both ends are inclusive.
start_dateequal toend_datereturns one row.No paging. A 730-day window returned 730 rows in 61631 bytes, with no
pageenvelope and nolinks.next. Bound the range yourself.links.selfechoes the parameters back, so two responses that differ only inproject_iddiffer in byte length while theirdatais identical.The dates must be
YYYY-MM-DD.03/02/2026is refused, whateverdate_component_orderinGET /preferencessays the site displays.
Measured by
047_site_facts_and_the_working_week(findings) — Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
rules:doors/findings-schema
Silent on this call
get_schedule_work_day_rules— One row per calendar day, both ends inclusive, no paging at 730 rows. Aproject_idoruser_idthat does not exist answers 200 with the studio default instead of an error.047_site_facts_and_the_working_week— Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
corpus/endpoints/get_schedule_work_day_rules.md
PUT /schedule/work_day_rules
One day per call, keyed by date in the body rather than by a path id, and user_id or project_id absent means the change applies to the studio default for everyone.
The success path is deliberately unexercised. A call with neither
user_idnorproject_idrewrites the studio calendar for every user of the site, and there is no dry run and no undo. The 400 rows above are what pins the parameter names; the 200 shape comes from the site's own/spec.json.Scope is decided by omission. No
user_idand noproject_idmeans studio-wide, which is the widest possible effect and also the shortest body. Send the scope explicitly.Every parameter is validated in one pass: a body wrong in two places lists both keys under
source.recalculate_fieldis what moves existing Task rows. Omitting it changes the calendar and leavesdurationanddue_datewhere they were, so the two can be made to disagree.Only
dateaddresses the row. There is no id, noDELETE, and no documented way to remove an exception other than writing the day back to what the work week says.The 200 response shape adds
projectanduserreferences that theGETrows do not have.
Measured by
047_site_facts_and_the_working_week(findings) — Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
rules:doors/findings-schema
Silent on this call
047_site_facts_and_the_working_week— Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
corpus/endpoints/put_schedule_work_day_rules.md
GET /spec.<format>
The site publishes its own OpenAPI v3 document, json or yaml, and it lists 62 operations where this corpus covers 23. The suffix is required and any other 406s.
servers[0].urlends in/api/v1.1, not/api/v1. The path this client uses is not the one the site advertises, and nothing in the corpus has yet measured whether the two differ.info.versionreads1.x, and the title has a trailing space. Neither is a useful version check;endpoints/get_rootreturns the real build.The spec is the authority for one deployment. The published documentation lists operations under different names,
PUT /entity/{entity}/{record_id}/_reviveandPOST .../_upload_completeamong them, that this site's spec does not have.191KB is too large to hand an agent whole. Diff it against the corpus and read the difference, which is what
probes/042_spec_coverage.pyprints.
Measured by
051_api_version(findings) — /api/v1 and /api/v1.1 are the same API. Across 20 read-only calls the only difference is api_version in the root document and the prefix each echoes in its own links. Any other segment is 404.
rules:doors/findings-protocol042_spec_coverage(findings) —GET /spec.jsonreturns the deployment's own OpenAPI v3 document. It advertises 62 operations against the 23 this corpus covers, and it disagrees with the published documentation.
rules:doors/findings-schema007_reference_disagrees_with_spec(reports) — Four calls in the published REST reference exist under no spelling in the deployment's own OpenAPI document, which names two of them differently.
rules:doors/reports
corpus/endpoints/get_spec_format.md
GET /subscription_seat/user_subscriptions
Returns a bare hash of user id to subscription string with no data and no links, holding only some HumanUser rows, and a null value means the user has no subscription rather than no such user.
The hash holds a subset of HumanUser rows and the endpoint gives no rule for which. On the probed site it held 14 of 24 HumanUser rows, and
sg_status_listdid not predict membership: 5 of 6actusers were present and 9 of 18disusers were too. Treat a missing key as unknown, not as "no subscription".nullis a value in the hash, not an absence. A user present withnulland a user absent are two different states and only the first one is stated.Keys are strings. Comparing them against an integer id from
/entity/human_usersmatches nothing.On the probed site the only non-
nullvalue wasnot_for_resale. The vocabulary of subscription names is site data; read it off the hash rather than hardcoding a list.The size of this hash is not
assignedfromGET /license_info. On the probed site they were 14 and 4.
Measured by
047_site_facts_and_the_working_week(findings) — Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
rules:doors/findings-schema
Silent on this call
047_site_facts_and_the_working_week— Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
corpus/endpoints/get_subscription_seat_user_subscriptions.md
POST /subscription_seat/user_subscriptions
Body is a bare hash of user id to subscription string, not a JSON:API document. An unknown id is a whole-request 400, and a hash naming no user is a 200 returning {}.
The success path is deliberately unexercised. Every call that could succeed changes the subscription of a real user of a real site, and there is no dry-run parameter and no undo. The rejections above are what pins the body shape; the 200 and 207 rows come from the site's own
/spec.json.{}answers 200 with{}. A caller that treats a 200 as "the assignment happened" cannot tell an applied change from a body that named nobody.The whole request fails on the first unknown id: the 400 replaces the per-user hash, so nothing is reported about the ids that were valid. Validate ids against
/entity/human_usersfirst.A per-user failure is a 207, not a 400, and its message is a string inside the hash rather than in an
errorsarray. A client checkingr.okpasses straight over it.The error message is in
title.detailisnull,sourceis{}.
Measured by
047_site_facts_and_the_working_week(findings) — Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
rules:doors/findings-schema
Silent on this call
047_site_facts_and_the_working_week— Three site-fact calls, three different envelopes:{data, status}, a bare hash, and JSON:API. Only/schedule/work_day_rulesreports a bad scope id, and it reports it as a 200.
corpus/endpoints/post_subscription_seat_user_subscriptions.md