SHINES CPS Intake — Proposed API
On this page
|
Proposed — net-new contract, not a legacy spec. SHINES has no intake API today (ADR-042 § Context). CRAIG defines this reasonable target contract and builds against a mock ( |
Overview
-
Transport: a single synchronous, durability-confirming HTTPS
POST. The edge is stateless (ADR-017), so the round-trip is the only durability receipt — no optimistic acks, no local buffering. The returned SHINES record id becomes the reporter’s tracking reference. -
Direction: CRAIG → SHINES only. SHINES has no signature capability; CRAIG → SHINES is an authenticated
POST— JSON when there are no attachments, andmultipart/form-datawhen a signed report carried supporting documents (ADR-042 §D9, #942). (3rd-party → CRAIG signing is a separate concern — see the keyring sidecar.) -
No status read: SHINES exposes no status-lookup contract in v1, and legacy record ids are guessable `NUMBER`s (not UUIDs), so a status-by-id route would be an enumeration vector. The tracking reference is a durable receipt the reporter keeps, not a lookup key (ADR-042 §D7).
Endpoint
POST /shines/cps-requests (the mock path; the real SHINES base path is #691).
Request body — two forms (the receiver content-negotiates on Content-Type):
-
application/json— aCpsRequestJSON object (below). The default; used whenever the submission carried no attachments. -
multipart/form-data— used when a signed report carried supporting documents (ADR-042 §D9, #942): arequestpart (the sameCpsRequestJSON, with a populatedsupportingDocuments) + one repeatedfilepart per attachment (the raw bytes,filename= the reporter’s sanitized file name). Each file’s SHA-256 is already verified at the CRAIG edge against the JWS-signedsupportingDocumentsmanifest before the forward, so SHINES MAY re-verify but need not.
Response (200, both forms):
{ "record_id": "100000001", "status": "accepted" }
record_id is a string (a legacy SHINES NUMBER; string avoids the leading-zero loss an integer would cause). status is the literal "accepted". Error shapes are #691.
CpsRequest — wire shape
The wire keys are the exact SHINES spellings emitted by the mapper, including the SHINES quirks isInfosterCare (lowercase f), chindInfo (the SHINES array name), and the questionChidDanger typo — preserved verbatim for fidelity. CRAIG-side struct fields are idiomatic snake_case; the mapper translates both names and values. The full field-by-field CRAIG→SHINES mapping + the RefVal crosswalk live in the plan (Appendix A/B).
Top-level scalars
| Wire key | Type | Source / notes |
|---|---|---|
|
string |
incident datetime (date portion) |
|
string |
reporter county |
|
string |
incident location |
|
string |
reporter’s relation |
|
string |
sanitized concern description |
|
string |
submit-time (date) |
|
string |
safety concerns |
|
string |
reporter phone (truncated) |
|
string |
caretaker block (ExtraFields) |
|
string |
secondary caretaker (ExtraFields) |
|
string |
ExtraFields |
|
string |
foster-care block (bool → |
|
string |
ExtraFields |
|
string |
screening flags ( |
|
string |
ICWA / Indian heritage |
|
string |
household-illness flag (bool → |
|
string |
ExtraFields |
|
string |
the nine narrative |
Nested objects
-
reporterInfo{ emailId, firstName, lastName, telephone, repType }—repTypeis the SHINES reporter-type free-text via the placeholder crosswalk (#692; the signed channel carries the non-public reporter types —professional/law_enforcement/self_report). -
motherInfo/fatherInfo(each optional, omitted when undesignated):{ firstName, lastName, dob, ssn, raceId?, maritalStatusId?, langId?, address, contactNumber, altContactInfo, isAllegedMaltreator }. The mother/father designation is captured, not derived (per-adultshines_household_role, ADR-042 §D4). -
otherHouseholdInfo[]— the*Infofields above plusrelToPrimaryCaretaker,isOtherMember(all remaining adults). -
chindInfo[]—{ firstName, lastName, dob, ssn, isVictim, sex, raceId?, grade }. -
supportingDocuments[]—{ fileName, fileType, sha256 }(always emitted;[]when none). The signed attachment manifest (ADR-042 §D9, #940):fileNamethe reporter’s file name,fileTypeits extension (e.g..pdf),sha256the lowercase-hex SHA-256 the reporter signed and the edge verified. On the multipart form the manifest travels in therequestpart while the bytes travel as thefileparts; the two are correlated bysha256.
ssn is a string (digits-only after stripping separators) — same leading-zero rationale as record_id. raceId/maritalStatusId/langId are numeric SHINES RefVal ids from a placeholder crosswalk (craig-reference::shines), swapped for the authoritative tables before go-live (#692); an absent enum omits the optional *Id key (no silent unwrap_or(0)). sex is M/F; booleans are Y/N (or empty for Option::None).
Idempotency + retry (decision, #701)
The proposed POST carries no idempotency key today, and ShinesSink performs exactly one POST per
submission with no retry (regression-locked by a sink unit test, forward_posts_exactly_once). So there is
no double-submit risk in the current design. When SHINES-outage buffering lands (the deferred
#215 local-outbox) and introduces a retry,
the contract MUST gain an idempotency key — e.g. carrying the intake received_request_id as a SHINES dedup key
— so a retry after an accepted-but-unacknowledged POST cannot create a duplicate CpsRequest. That key is part
of the authoritative contract negotiation (#691); CRAIG does not invent it ahead of SHINES. Until then, the
exactly-one-POST invariant is the guarantee.
Related
-
ADR-042 — the backend-profile architecture + the SHINES mapping decision.
-
Standalone intake architecture — how a submission reaches this API.
-
craig-intake-keyring sidecar — the no-partner-tenancy signed-API auth.
-
Go-live data dependencies: #691 (this contract), #692 (RefVals), #693 (required-field set).