{"openapi":"3.1.0","info":{"title":"First Mile Labs Public Pull API","version":"1.0.0","description":"Read-only OAuth2 client-credentials API for pulling KYB/KYC case, document, decision, screening, and event data into your CRM, data warehouse, or case-management system.\n\nMirrors the additive compatibility policy of outbound webhooks (`eventVersion: 1`). Breaking changes ship as a new `/v2/*` namespace.","contact":{"name":"First Mile Labs Support","email":"support@firstmilelabs.com","url":"https://www.firstmilelabs.com/docs/api"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://www.firstmilelabs.com","description":"Production"}],"security":[{"BearerAuth":[]}],"tags":[{"name":"OAuth","description":"Token issuance"},{"name":"Cases"},{"name":"Documents"},{"name":"Events"},{"name":"Decisions"},{"name":"Screening"}],"paths":{"/v1/oauth/token":{"post":{"tags":["OAuth"],"summary":"Exchange client credentials for an access token.","description":"RFC 6749 §4.4 client_credentials grant. Tokens are JWTs valid for 1h.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenRequest"}},"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/TokenRequest"}}}},"responses":{"200":{"description":"Access token issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"400":{"$ref":"#/components/responses/OAuthError"},"401":{"$ref":"#/components/responses/OAuthError"}}}},"/v1/cases":{"get":{"tags":["Cases"],"summary":"List cases for the authenticated tenant.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"status","in":"query","schema":{"type":"string"}},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Return cases whose derived `updatedAt` is >= this timestamp."},{"name":"created_since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Return cases whose `createdAt` is >= this timestamp."}],"responses":{"200":{"description":"Page of cases.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Case"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/cases/{caseId}":{"get":{"tags":["Cases"],"summary":"Get a single case with its current decision and risk band.","parameters":[{"name":"caseId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Case detail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CaseDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/cases/{caseId}/documents":{"get":{"tags":["Documents"],"summary":"List documents for a single case (cursor-paginated).","parameters":[{"name":"caseId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"Page of case documents.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Document"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/cases/{caseId}/decisions":{"get":{"tags":["Decisions"],"summary":"Decision history for a case (cursor-paginated).","parameters":[{"name":"caseId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"Page of decisions.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Decision"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/cases/{caseId}/screening":{"get":{"tags":["Screening"],"summary":"Screening hits for a case (cursor-paginated).","parameters":[{"name":"caseId","in":"path","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"}],"responses":{"200":{"description":"Page of screening hits.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/ScreeningHit"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/documents":{"get":{"tags":["Documents"],"summary":"List documents across the tenant or for a single case.","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"caseId","in":"query","schema":{"type":"string"}},{"name":"status","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Page of documents.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Document"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/documents/{id}":{"get":{"tags":["Documents"],"summary":"Get a single document with extracted data.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"Document detail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Document"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/v1/events":{"get":{"tags":["Events"],"summary":"Pull-style event feed (mirrors outbound webhook payloads).","description":"Returns each distinct event exactly once (deduped by `eventId`). Useful when push delivery is impractical (firewalled environments, batch ETL pipelines, polling iPaaS recipes).","parameters":[{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Cursor"},{"name":"event_type","in":"query","schema":{"type":"string"},"description":"Filter by event type, e.g. `case.decision.made`."},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Page of events.","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Page"},{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Event"}}}}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"parameters":{"Limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"Cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque cursor returned in `nextCursor` from a previous page."}},"responses":{"Unauthorized":{"description":"Invalid or missing access token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}},"NotFound":{"description":"Resource not found (or not in the authenticated tenant).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"}}}}}},"OAuthError":{"description":"OAuth2 error response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthError"}}}}},"schemas":{"TokenRequest":{"type":"object","required":["grant_type","client_id","client_secret"],"properties":{"grant_type":{"type":"string","enum":["client_credentials"]},"client_id":{"type":"string"},"client_secret":{"type":"string"},"scope":{"type":"string","description":"Space-separated subset of granted scopes."}}},"TokenResponse":{"type":"object","required":["access_token","token_type","expires_in","scope"],"properties":{"access_token":{"type":"string"},"token_type":{"type":"string","enum":["Bearer"]},"expires_in":{"type":"integer","description":"Seconds until expiry."},"scope":{"type":"string"}}},"OAuthError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"error_description":{"type":"string"}}},"Page":{"type":"object","required":["data","hasMore"],"properties":{"data":{"type":"array","items":{}},"nextCursor":{"type":["string","null"]},"hasMore":{"type":"boolean"}}},"Case":{"type":"object","required":["caseId","status","createdAt"],"properties":{"caseId":{"type":"string"},"orgId":{"type":["string","null"]},"companyName":{"type":["string","null"]},"companyNumber":{"type":["string","null"]},"country":{"type":["string","null"]},"entityType":{"type":["string","null"]},"status":{"type":"string"},"owner":{"type":["object","null"]},"customerEmail":{"type":["string","null"]},"createdAt":{"type":"string","format":"date-time"},"confirmedAt":{"type":["string","null"],"format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"crmRecordId":{"type":["string","null"]},"source":{"type":["string","null"]}}},"CaseDetail":{"allOf":[{"$ref":"#/components/schemas/Case"},{"type":"object","properties":{"decision":{"oneOf":[{"type":"null"},{"$ref":"#/components/schemas/Decision"}]},"risk":{"oneOf":[{"type":"null"},{"type":"object","properties":{"compositeScore":{"type":["number","null"]},"band":{"type":["string","null"]},"assessedAt":{"type":["string","null"],"format":"date-time"}}}]}}}]},"Decision":{"type":"object","required":["outcome","decidedAt"],"properties":{"outcome":{"type":"string","enum":["approved","declined"]},"notes":{"type":["string","null"]},"analystEmail":{"type":["string","null"]},"decidedAt":{"type":"string","format":"date-time"}}},"Document":{"type":"object","required":["id","caseId","status","uploadedAt"],"properties":{"id":{"type":"integer"},"caseId":{"type":"string"},"documentType":{"type":["string","null"]},"filename":{"type":["string","null"]},"mimeType":{"type":["string","null"]},"sizeBytes":{"type":["integer","null"]},"status":{"type":"string"},"confidence":{"type":["string","null"]},"uploadedAt":{"type":"string","format":"date-time"},"warnings":{"type":"array","items":{}},"notes":{"type":["string","null"]},"extractedData":{"type":["object","null"]}}},"ScreeningHit":{"type":"object","required":["hitId","subjectName"],"properties":{"hitId":{"type":"string"},"subjectName":{"type":"string"},"role":{"type":["string","null"]},"schema":{"type":["string","null"]},"isPep":{"type":"boolean"},"isSanctioned":{"type":"boolean"},"score":{"type":["number","null"]},"topics":{"type":"array","items":{}},"position":{"type":"array","items":{}},"nationality":{"type":"array","items":{}},"observedAt":{"type":"string","format":"date-time"}}},"Event":{"type":"object","description":"Mirrors the outbound webhook envelope so a partner writes one payload mapper for both push and pull. The shape of `data` for each `eventType` is defined by the per-event JSON Schemas published at `/schemas/webhooks/<eventType>.v1.json` (index at `/schemas/webhooks/index.json`). When a future `eventVersion: 2` ships, a new `/v2/events` namespace will appear alongside this one.","required":["eventId","eventType","eventVersion","occurredAt","data"],"properties":{"eventId":{"type":"string","description":"Stable idempotency key — same id push and pull receivers see."},"eventType":{"type":"string","description":"Event type, e.g. `case.created`. See `/schemas/webhooks/index.json` for the full enum.","examples":["case.created","case.submitted","case.decision.made","case.status.changed","data.corrected","document.uploaded","screening.completed","ai_review.completed"]},"eventVersion":{"type":"integer","enum":[1]},"occurredAt":{"type":"string","format":"date-time"},"data":{"type":"object","description":"Full webhook payload. Validated against the per-event JSON Schema at `/schemas/webhooks/<eventType>.v1.json`."}}}}}}