Reply
Reply is site-wide with no project field, and entity accepts almost every type on the site, not only Note; send entity on create, because a Reply whose entity is null cannot be deleted.
Type Schema name Reply, REST path slug replies. Both the English plural and the naive one resolve,
along with the singular and the schema name, so a client that guessed wrong still works.
| path | result |
|---|---|
GET /entity/replies |
200; links.self normalises to /api/v1/entity/replies/<id> |
GET /entity/replys, GET /entity/Replies |
200, the same rows |
GET /entity/reply, GET /entity/Reply |
200, the same rows |
GET /entity/repliess |
404 Entity type 'repliess' does not exist. |
Site-wide: seven fields, none of them project. A Reply takes its project from whatever its entity
points at, and there is no way to ask for one project's replies directly.
| project scope attempted | result |
|---|---|
_search [["project", "is", {"type": "Project", "id": N}]] |
400 code 103 API read() Reply.project doesn't exist. |
GET ?filter[project]=N |
400, same title, source {"Reply.project": " does not exist. …"} |
GET ?project_id=N |
200, accepted and ignored |
POST /entity/replies with a project key |
400 API create() Reply.project doesn't exist. |
Identity content, display name Reply Text, data_type: text. It is the body and the whole of the
row a human reads; there is no subject, title or code. Nothing is flagged unique, and content is the
only field flagged mandatory, which the create contract below contradicts. cached_display_name is
filled from content at create time and is what a Note.replies link returns as its name.
Create POST /entity/replies, Content-Type: application/json. No field is required.
| body sent | result |
|---|---|
{} |
201, content null, entity null, publish_status "published" |
{"project": {"type": "Project", "id": N}} |
400 API create() Reply.project doesn't exist. |
{"content": "…"} |
201, orphaned: entity null |
{"entity": {"type": "Note", "id": N}} |
201, linked, content null |
{"entity": {"type": "Note", "id": N}, "content": "…"} |
201, the usable call |
{"entity": {"type": "Version", "id": N}, "content": "…"} |
201, linked to the Version |
{"entity": N, "content": "…"} |
400 API create() Reply.entity expected [Hash, ActiveSupport::HashWithIndifferentAccess, … NilClass] data type(s) but got Integer: N |
publish_status is a plain text field the server sets to published; it is editable and is not a
list, so it has no vocabulary to validate against. A PUT afterwards changes content, clears it with
null, and repoints entity at a different type, all at 200.
Links Two entity fields and no multi_entity field. Both are written as a {type, id} hash under
relationships (field_types/entity).
| field | data type | valid_types |
editable |
|---|---|---|---|
entity |
entity | every type on the site bar one | yes |
user |
entity | ['HumanUser', 'ApiUser', 'ClientUser'] |
yes |
Reply.entity is a generic any-entity link, not a Note link. On the probed site it names 113 of the 114
types in /schema, the exception being one connection type. Note, Delivery, Version and Reply
itself are all in the list, and a create against Note, Version and Delivery each answered 201: a
Reply hung off a Delivery is as legal as one hung off a Note, and the external claim that replies
appear on Deliveries is what the schema says. Read valid_types for the set; it is site configuration.
Exactly two fields anywhere in /schema have valid_types of ['Reply'], and both are multi_entity
and editable:
| field | holds |
|---|---|
Note.replies |
the Note's thread |
Delivery.replies |
the Delivery's thread |
Every other type takes replies through Reply.entity with no reverse field to read them back from, so for
those a client filters Reply instead:
POST /entity/replies/_search Content-Type: application/vnd+shotgun.api3_array+json
{"filters": [["entity", "is", {"type": "Version", "id": <id>}]],
"fields": ["content", "created_at", "user"], "sort": ["id"]}
Order Replies are returned oldest first and there is no ordering field of their own.
| read | order |
|---|---|
_search with no sort |
id ascending, which matched created_at ascending on every row measured |
sort: ["id"], sort: ["created_at"] |
the same order |
sort: ["-id"], sort: ["-created_at"] |
reversed |
Note.replies on a GET of the Note |
the same order as sort: ["id"] |
Sort on id, not on created_at. created_at has one-second resolution, and three replies posted inside
one second all read the same timestamp, leaving their order undefined.
Status None. Reply has no status_list and no list field. publish_status is text, not a status.
Traps
- A Reply whose
entityis null cannot be deleted.DELETE /entity/replies/<id>answers 400 code 104Delete failed for [Reply with id=N]: undefined method 'reflect_on_association' for class NilClass. Assign anyentitywith aPUTand the same delete answers 204. A create that omittedentityis therefore permanent litter until it is repaired, so sendentityin the create body. contentis flaggedmandatoryand an empty body still answers 201, the same inversion probe 012 found on Version. The flag is not the contract.- A filter for a type that is not in
Reply.entity'svalid_types400s withAPI read() invalid/missing entity hash string 'type', even when live rows point at it. On the probed site the oldest replies hang offTicket, a type absent from/schema, and the 400's ownValid entity typeslist namesTicketwhile the filter is refused. [["entity", "type_is", "Ticket"]]returns those rows at 200; usetype_isfor a type-level cut and reserve the hash for a type you read out ofvalid_types.entityis editable after the fact, with no type check against the original. APUTmoves a Reply from a Note to a Version at 200 and the Note's thread silently loses a row.