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 (tools/craig-mock-server, POST /shines/cps-requests); the authoritative contract — exact field names/types, auth, error shape — is a blocking go-live dependency tracked as #691. This page is the source-of-truth derived from the CRAIG CpsRequest mapper (services/craig-intake/src/backend/shines/mapper.rs) + the mock; swapping in the real SHINES contract is a localized mapper/crosswalk change.

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, and multipart/form-data when 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 — a CpsRequest JSON 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): a request part (the same CpsRequest JSON, with a populated supportingDocuments) + one repeated file part 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-signed supportingDocuments manifest 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

incidentDate

string

incident datetime (date portion)

county

string

reporter county

location

string

incident location

reporterRelationship

string

reporter’s relation

narrative

string

sanitized concern description

dateReported / intakeDate

string

submit-time (date)

emergencyInfo

string

safety concerns

reporterContactNumber

string

reporter phone (truncated)

primaryCaretaker (+Address, +Relationship)

string

caretaker block (ExtraFields)

secCaretaker (+Address)

string

secondary caretaker (ExtraFields)

firstHandInfo

string

ExtraFields

isInfosterCare, fosterCareName, fosterCareAddress

string

foster-care block (bool → Y/N)

familyName

string

ExtraFields

isInMilitary, isInMilitaryParent, isPreviouslyReported

string

screening flags (Y/N/empty)

isNaorit, isNaoritComments

string

ICWA / Indian heritage

isAnyoneIll

string

household-illness flag (bool → Y/N/empty) (ExtraFields, #935)

isAnyoneIllComments

string

ExtraFields

questionMalNegOrAbusequestionFswsComments, questionOccurAgain, questionChidDanger

string

the nine narrative question* fields

Nested objects

  • reporterInfo { emailId, firstName, lastName, telephone, repType }repType is 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-adult shines_household_role, ADR-042 §D4).

  • otherHouseholdInfo[] — the *Info fields above plus relToPrimaryCaretaker, 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): fileName the reporter’s file name, fileType its extension (e.g. .pdf), sha256 the lowercase-hex SHA-256 the reporter signed and the edge verified. On the multipart form the manifest travels in the request part while the bytes travel as the file parts; the two are correlated by sha256.

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.

Edit this page · latest