Endpoints — Search
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.
POST /entity/<type>/_search
The only way to send a filter the query string cannot express, and it refuses application/json at 415 naming both vendor types. api3_array cannot express or; api3_hash nests.
| you send | what happens |
|---|---|
a query-string filter[] |
ignored. Only the body filters here |
{"path", "relation", "values"} as a condition |
400 Missing logical operator. That shape runs nowhere |
filters: [] |
200, unscoped, every row on the site |
an or under api3_array |
not expressible; the array form is and only |
- The unknown-operator 400 enumerates the legal set for that field's data type. It is the cheapest way to discover the vocabulary, and it is where the site's filter matrix comes from.
Measured by
004_array_vs_hash(findings) — api3_array/api3_hash are a POST _search request Content-Type, not a GET Accept header: as Accept they 406, and entity fields are returned under relationships either way.
rules:doors/findings-protocol028_loud_and_silent(findings) — A 400 is trustworthy and usually names the legal set, but a 200 proves nothing: an unknown field, sort key or query param is a no-op, and a batch can return an id for a row it never made.
rules:doors/findings-protocol062_cors(findings) — Every path under/api/v1answers the preflight and echoes anyOrigin, credentials true./internal_apiand the web paths send no CORS header, so a page on another origin proxies those.
rules:doors/findings-protocol061_shipped_statuses(findings) — Nothing in the schema marks a shipped Status.systemis true on a minority of them; the stock set iscreated_by is null, plusoptions[return_only]=retiredfor the rows a site retired.
rules:doors/findings-schema003_query(findings) — A dotted ?fields path comes back flat under literal key "sg_task.Task.content" in attributes; an entity field is returned under relationships as {data, links}. Never read a row from attributes alone.
rules:doors/findings-read006_pagination(findings) — links.next is emitted on every page forever, including zero-row ones, so stop paging when data is empty and never on a missing next.
rules:doors/findings-read018_project_listing(findings) — sg_status is not a liveness filter and is null on 15 of 22 projects; is_template, is_demo and archived are the discriminators, so pick the ones your list wants - is_demo hides the demo show.
rules:doors/findings-read021_media_resolution(findings) — PublishedFile.path is returned with the LocalStorage join already done, so a client never reads LocalStorage or reassembles a root, but a platform whose storage root is unset reads null.
rules:doors/findings-read023_pages(findings) — A page's layout is the PageSetting row whose user is null; settings_json reads back as decoded JSON and body/list_content settings.columns is the column list. Every filter on it is ignored.
rules:doors/findings-read026_result_order(findings) — Rows come back id ascending unless you sort; ["id", "in", [...]] discards the order of the list, and an unsortable or unknown sort field is a silent 200 no-op where the same name in a filter 400s.
rules:doors/findings-read059_dotted_path_type_check(findings) — The middle segment of a dotted path is checked against the field's valid_types in a projection and against the schema alone in a filter: the projection drops the key at 200, the filter 400s.
rules:doors/findings-read060_entity_dict_name(findings) — Thenamein an entity dict is the target'scached_display_name, filled on every type measured, single and multi alike. Read it, not the per-type identity field, and expect decoration.
rules:doors/findings-read016_dotted_multi_entity(findings) — A dotted path through a multi_entity field reads back nothing: HTTP 200 with the key silently absent from attributes. Filters on that same path work, including two hops.
rules:doors/findings-filter017_filter_operators(findings) — is/is_not/contains/not_contains/starts_with/ends_with/in/not_in all work, on text fields and through dotted paths; an unknown operator 400s on all 21 data types, naming the valid list on 16.
rules:doors/findings-filter030_complex_filters(findings) — api3_hash nests and/or groups 265 deep and mixes leaves with sub-groups; api3_array cannot express or, query-string filter[] is ignored on _search, and {path,relation,values} runs nowhere.
rules:doors/findings-filter068_note_read_state(findings) — read_by_current_user is per person and missing from the schema;isandis_notare evaluated, whilein,not_inand an unknownisvalue all return the unread rows at 200.
rules:doors/findings-filter071_note_link_name_filter(findings) — Filter notes about a thing onnote_links.<Type>.cached_display_name: it resolves for every valid type,code400s on Booking andnameon all but Department. The path cannot be read back.
rules:doors/findings-filter069_client_note(findings) —client_notecannot be set over REST:trueon create is 400 and anyPUTis 400editable on create only.sg_note_type: "Client"is the one marker a caller can write.
rules:doors/findings-write014_attach_file(findings) — Leave the field out of the _upload path and the file is stored as an Attachment on attachment_links; read it back with POST /entity/attachments/_search, never flat filter[].
rules:doors/findings-upload025_event_log(findings) — meta.old_value and meta.new_value answer "what was this before", but meta is unfilterable and unsortable: narrow on entity, event_type and attribute_name, sort -id, read meta yourself.
rules:doors/findings-observe049_script_events(findings) — A script's writes reach the event log only while its ApiUser has generate_event_log_entries True. The default is False and nothing errors when off. One create logs one row per field plus one _New.
rules:doors/findings-observe003_query_fields_and_pages(recipes) — Resolve a query field's value, and run the rows a saved Page shows
rules:doors/recipes004_register_published_file(recipes) — Register the next PublishedFile without overwriting the last one, and write a path the server resolves for every platform
rules:doors/recipes005_propagate_status(recipes) — Roll a status up from a parent's Tasks and Versions onto the parent, without racing a concurrent write
rules:doors/recipes007_build_and_reconcile_a_cut(recipes) — Write a Cut and its CutItems from an edit, read the timeline back, and reconcile a second edit against the Cut already there
rules:doors/recipes009_multi_entity_safely(recipes) — Add to and remove from a multi_entity field without destroying the links you did not mean to touch
rules:doors/recipes010_status_picker(recipes) — List the statuses a project actually offers, each with the label, colour and icon needed to draw it
rules:doors/recipes014_notes_about(recipes) — Find the Notes about a Shot, Asset or Version by the name of the thing, and read what each Note is linked to
rules:doors/recipes003_sort_fails_silently(reports) — A sort on an unknown or unsortable field answers 200 with the rows in default order, while the same field name in a filter answers 400 and names the reason.
rules:doors/reports008_jsonb_filters_return_everything(reports) — A filter on PageSetting.settings_json or EventLogEntry.audit_trail is accepted and ignored, so the unfiltered set comes back at 200 and is_null and is_not_null each return every row.
rules:doors/reports
Silent on this call
post_entity_type_search— The only way to send a filter the query string cannot express, and it refusesapplication/jsonat 415 naming both vendor types.api3_arraycannot expressor;api3_hashnests.028_loud_and_silent— A 400 is trustworthy and usually names the legal set, but a 200 proves nothing: an unknown field, sort key or query param is a no-op, and a batch can return an id for a row it never made.023_pages— A page's layout is the PageSetting row whose user is null; settings_json reads back as decoded JSON and body/list_content settings.columns is the column list. Every filter on it is ignored.026_result_order— Rows come back id ascending unless you sort; ["id", "in", [...]] discards the order of the list, and an unsortable or unknown sort field is a silent 200 no-op where the same name in a filter 400s.059_dotted_path_type_check— The middle segment of a dotted path is checked against the field's valid_types in a projection and against the schema alone in a filter: the projection drops the key at 200, the filter 400s.016_dotted_multi_entity— A dotted path through a multi_entity field reads back nothing: HTTP 200 with the key silently absent from attributes. Filters on that same path work, including two hops.017_filter_operators— is/is_not/contains/not_contains/starts_with/ends_with/in/not_in all work, on text fields and through dotted paths; an unknown operator 400s on all 21 data types, naming the valid list on 16.030_complex_filters— api3_hash nests and/or groups 265 deep and mixes leaves with sub-groups; api3_array cannot express or, query-string filter[] is ignored on _search, and {path,relation,values} runs nowhere.068_note_read_state— read_by_current_user is per person and missing from the schema;isandis_notare evaluated, whilein,not_inand an unknownisvalue all return the unread rows at 200.025_event_log— meta.old_value and meta.new_value answer "what was this before", but meta is unfilterable and unsortable: narrow on entity, event_type and attribute_name, sort -id, read meta yourself.049_script_events— A script's writes reach the event log only while its ApiUser has generate_event_log_entries True. The default is False and nothing errors when off. One create logs one row per field plus one _New.005_propagate_status— Roll a status up from a parent's Tasks and Versions onto the parent, without racing a concurrent write009_multi_entity_safely— Add to and remove from a multi_entity field without destroying the links you did not mean to touch
corpus/endpoints/post_entity_type_search.md
POST /entity/<type>/_summarize
Counts without paging rows. One grouping returns a field's distinct values and their counts at ~300ms, so rank a shortlist with it and never scan every field.
Summarizing an unsummarizable field,
image, answers 200 with a 37-byte body and no summary. It does not 400. Test that the key you asked for is insummariesbefore reading it.group_nameis the rendered label andgroup_valuethe raw one. For atimecodefield the rendered form isHH:MM:SS:FF, which is how the frame rate is recovered when no field exposes it.One call per field at about 300ms. Over 71 fields that is 21 seconds. Rank a shortlist by fill rate first and summarize only the candidates.
Measured by
028_loud_and_silent(findings) — A 400 is trustworthy and usually names the legal set, but a 200 proves nothing: an unknown field, sort key or query param is a no-op, and a batch can return an id for a row it never made.
rules:doors/findings-protocol006_pagination(findings) — links.next is emitted on every page forever, including zero-row ones, so stop paging when data is empty and never on a missing next.
rules:doors/findings-read021_media_resolution(findings) — PublishedFile.path is returned with the LocalStorage join already done, so a client never reads LocalStorage or reassembles a root, but a platform whose storage root is unset reads null.
rules:doors/findings-read020_summarize(findings) — _summarize needs the same vendor Content-Type as _search, and onegroupingcall returns a field's distinct-value count and its empty count. At ~300ms a field, rank a shortlist, never scan.
rules:doors/findings-filter030_complex_filters(findings) — api3_hash nests and/or groups 265 deep and mixes leaves with sub-groups; api3_array cannot express or, query-string filter[] is ignored on _search, and {path,relation,values} runs nowhere.
rules:doors/findings-filter068_note_read_state(findings) — read_by_current_user is per person and missing from the schema;isandis_notare evaluated, whilein,not_inand an unknownisvalue all return the unread rows at 200.
rules:doors/findings-filter071_note_link_name_filter(findings) — Filter notes about a thing onnote_links.<Type>.cached_display_name: it resolves for every valid type,code400s on Booking andnameon all but Department. The path cannot be read back.
rules:doors/findings-filter003_query_fields_and_pages(recipes) — Resolve a query field's value, and run the rows a saved Page shows
rules:doors/recipes014_notes_about(recipes) — Find the Notes about a Shot, Asset or Version by the name of the thing, and read what each Note is linked to
rules:doors/recipes
Silent on this call
028_loud_and_silent— A 400 is trustworthy and usually names the legal set, but a 200 proves nothing: an unknown field, sort key or query param is a no-op, and a batch can return an id for a row it never made.030_complex_filters— api3_hash nests and/or groups 265 deep and mixes leaves with sub-groups; api3_array cannot express or, query-string filter[] is ignored on _search, and {path,relation,values} runs nowhere.068_note_read_state— read_by_current_user is per person and missing from the schema;isandis_notare evaluated, whilein,not_inand an unknownisvalue all return the unread rows at 200.
corpus/endpoints/post_entity_type_summarize.md
POST /entity/_text_search
Free-text search across several types at once, returning a flattened row that is not the _search shape. entity_types is required and its value doubles as the per-type filter.
There is no
fieldsparameter. Every row isname,linksandstatus, whatever the type, so a client that needs more re-reads the row by itslinks.self.attributes.linksis a two-element array of strings, the linked row's type and its name, and it is["", ""]for a type that links to nothing. It is not an entity reference and cannot be followed.entity_typesmaps a type to a filter, so one call can be scoped differently per type. That is the only place in the API where a filter is keyed by the type it applies to.The shape is checked per key, so one call cannot mix the two forms. The key holding the value the
Content-Typedoes not name decides the 400, and no type answers rows.One bad key fails the whole call: a field the type lacks, an operator its data type lacks, or a key no entity type is named by is 400 for every type in the map, not a type dropped from the answer.
A group under
api3_hashmay hold another group, to at least three levels, andorreturns the union of its branches. The array form takes basic condition arrays alone, and two of them are theandof both.The response has no
links, so paging ispage.numberand there is nothing that says a further page exists. Ask untildatais empty.textis matched case-insensitively against the row's name and against the name of the row underattributes.links. It is not matched againstdescription.
Measured by
046_search_without_a_path(findings) —/hierarchy/_expandand/hierarchy/_searchrefuse the vendor content types every other POST requires and takeapplication/jsonalone, so one shared POST helper 415s on half the API.
rules:doors/findings-filter053_text_search_matching(findings) —page.sizecaps at 25 and defaults to 25 with nolinks, so page withpage.number. Every word must match a case-insensitive substring of the name or of the linked row's name.
rules:doors/findings-filter063_text_search_filter_shape(findings) — Anentity_typesvalue follows the request Content-Type: an array of triples under api3_array, alogical_operatorgroup under api3_hash, which alone nests. The other shape is 400 code 103.
rules:doors/findings-filter
Silent on this call
post_entity_text_search— Free-text search across several types at once, returning a flattened row that is not the_searchshape.entity_typesis required and its value doubles as the per-type filter.053_text_search_matching—page.sizecaps at 25 and defaults to 25 with nolinks, so page withpage.number. Every word must match a case-insensitive substring of the name or of the linked row's name.
corpus/endpoints/post_entity_text_search.md
POST /hierarchy/_expand
Returns one level of the navigation tree the web interface draws. It refuses the vendor content types every other POST requires and accepts only application/json.
The content type is inverted.
_search,_summarizeand_text_searchrefuseapplication/jsonand demand a vendor type;/hierarchy/*does the exact opposite. A client with one shared POST helper gets 415 on whichever half it did not write first.One level per call.
childrennames the next paths andhas_childrensays which are worth expanding, so walking a project is one call per node.Code 107 appears here and nowhere else in the corpus. It is a lookup that found the wrong number of rows, not a malformed request.
seed_entity_fieldchanged nothing on the probed site. Omit it until something shows it matters.A child has no
pathwhen itsref.kindisempty:{"label": "No Shots", "ref": {"kind": "empty", "value": null}, "has_children": false}is the placeholder for a level with nothing under it, and it is a child like any other. Readpathwith a default.ref.kindisentityfor a row or a group that is one,entity_typefor the ungrouped bucket,listfor a group that is a list value, andemptyfor the placeholder.The
__none__segment is reachable at two spellings._expandwrites<field>/<GroupType>/__none__and_searchreturns<field>/__none__; both answer the same rows, and the label is templated off the segment, so the second readsShots with no __none__.A path is answerable whether or not
childrennamed it. Expanding a level whose grouping field has no rows answers oneemptychild, and the__none__path under that level still answers its rows.
Measured by
064_hierarchy_expand_buckets(findings) — Dedupechildrenbypathand keep the first. The__none__bucket is repeated once per group, byte-identical every time, and its rows are disjoint from every group's.
rules:doors/findings-read046_search_without_a_path(findings) —/hierarchy/_expandand/hierarchy/_searchrefuse the vendor content types every other POST requires and takeapplication/jsonalone, so one shared POST helper 415s on half the API.
rules:doors/findings-filter
corpus/endpoints/post_hierarchy_expand.md
POST /hierarchy/_search
Answers where a row sits in the navigation tree. search_criteria must be a hash keyed exactly entity, and every other shape is the same misleading size must be 1.
size must be 1 does not mean what it says. Every one of these has one key and is refused:
sent as search_criteria |
result |
|---|---|
{"entity": {"type": "Shot", "id": 862}} |
200 |
{"entity_type": "Shot"} |
400 size must be 1 |
{"Shot": 862} |
400 size must be 1 |
{"Shot": [862]} |
400 size must be 1 |
[{"entity_type": "Shot"}] |
400 must be a hash |
The key has to be the literal string
entity. The error counts keys it recognises, not keys you sent, so an unrecognised key reads as a size problem and never names itself.incremental_pathis the breadcrumb, one entry per level, and the last is the row.path_labelis the same thing rendered for a person and it omits the project.The path goes through
sg_sequence, a field name, so the tree follows the site's own navigation configuration rather than a fixed hierarchy.A row with nothing in the grouping field is returned as
/Project/<id>/Shot/sg_sequence/__none__, without the type segment_expandputs there. Both spellings answer the same rows on_expand.
Measured by
064_hierarchy_expand_buckets(findings) — Dedupechildrenbypathand keep the first. The__none__bucket is repeated once per group, byte-identical every time, and its rows are disjoint from every group's.
rules:doors/findings-read046_search_without_a_path(findings) —/hierarchy/_expandand/hierarchy/_searchrefuse the vendor content types every other POST requires and takeapplication/jsonalone, so one shared POST helper 415s on half the API.
rules:doors/findings-filter
corpus/endpoints/post_hierarchy_search.md