ASI Database Node API

Application API v1

Base URL: https://asi-database-node-api.arybit.co.ke. Application routes require APPLICATION_API_ENABLED=1 and the selected engine auth stream (Database/mysql-auth or Database/postgresql-auth). They are absent by default. This is a proposed PHP integration contract tested against real databases; the PHP source is unavailable.

Each request uses Authorization: Bearer credential_id.secret. A registered, enabled, unexpired service identity supplies its tenant and explicit scopes. Callers cannot select tenants, roles, SQL, table names or connection parameters. No OAuth issuer or token exchange is advertised. The static OpenAPI file is exported from FastAPI routes with python tools/export-openapi.py; use --check to detect drift. Production disables interactive schema endpoints.

Method and route Scope Body / result
GET /health, /healthz, /version, / none Process/build diagnostics; gateway redirects / to console
GET /ready none 200/503 connectivity; application mode checks contract columns and migration checksums
GET /api/v1/status diagnostics:read Configured engine, server version, schema readiness
POST /api/v1/users/lookup users:read {email} → user_id,email,username,status,verified
GET /api/v1/users/{user_id} users:read Same minimal user representation
POST /api/v1/users users:create {email,username,password_hash} → 201 {user_id}
PATCH /api/v1/users/{user_id} users:update {username} → {user_id}
POST /api/v1/auth/credentials/verify-context auth:credential-verify {email} → user_id,status,verified,locked_until
POST /api/v1/auth/events auth:events:write {user_id,success} → 204
POST /api/v1/sessions sessions:write {user_id,token_hash,expires_at} → 201 {session_id}
GET /api/v1/sessions/{session_id} sessions:read session_id,user_id,expires_at,revoked,active
POST /api/v1/sessions/{session_id}/revoke sessions:revoke No body → 204; repeat revocation succeeds
POST /api/v1/verification-tokens verification:write {user_id,token_hash,expires_at,purpose} → 204
POST /api/v1/verification-tokens/consume verification:consume {token_hash,password_hash?} → 204

UUIDs use validated strings. Emails are ASCII, lowercased before lookup/storage, with exact binary database comparison. Username accepts 3–50 ASCII letters, digits or underscores; uniqueness uses binary/C collation on both engines. User creation accepts encoded PHP bcrypt or Argon2 hashes, never plaintext. The API stores the hash unchanged. Only asi-authenticate-node-api may hold the credential status scope. No HTTP route returns password hashes. The actual client password verification contract is missing; credential context alone cannot authenticate a password. Existing clients expecting a hash require a reviewed migration before onboarding. See v14 acceptance limitations.

Eight reported failures lock the user for 15 minutes. Successful events cannot clear an active lock. Session creation requires an active, unlocked user. Session expiry must include a timezone and fall within 30 days; persisted times are UTC. Session reads calculate active from expiry and revocation. A session ID is a lookup identifier, not proof that a presented token is valid: PHP must validate the token cryptographically and bind its session ID. No token validation bypass or raw-token retrieval exists.

Token hashes must be lowercase SHA-256 hex of high-entropy random tokens generated by the consumer. Verification purposes are signup and password_reset; expiry is at most one hour. Consumption locks the owning user and token, rejects expiry or replay, and atomically marks signup verification or updates the password hash. Password reset requires a hash and revokes all user sessions and outstanding password-reset records. Signup rejects a supplied hash. No short numeric-code, refresh exchange, bulk export or arbitrary-table contract is claimed.

Errors have {error:{code,message,request_id}}: 401 invalid identity; 403 missing scope; 404 missing or foreign-tenant resource; 409 uniqueness, lockout or consumed token; 413 body over 16 KiB; 422 invalid input; 429 admission limit; 503 database dependency failure; 500 unexpected failure. Validation errors omit submitted values. Responses have Cache-Control: no-store and a generated X-Request-ID. There are no paginated collection endpoints.

The process admits at most 600 application requests per minute, including failed authentication. This bounded global limit is an overload guard, not a distributed per-service quota. Configure gateway quotas for multiple workers/replicas.

There is no Idempotency-Key response cache. User email/username and session/token hash uniqueness prevent duplicate records; repeats return 409. Consumers must reconcile uncertain creates through authorized lookup. Revoke is idempotent; authentication events and one-time consume must never be blindly retried. Only safe lookups may be automatically retried with bounded backoff.

The separate /setup-api/console/ interface uses the operator token, not service credentials. See console contract. It is outside the application contract. Versioned changes should be additive; incompatible changes require a new version and an explicit consumer migration. See PHP integration.

Shared direct data and service quotas

User lookup and username update now call shared database routines; other transaction logic resides in core/application_service.py. Direct TLS access operates on the same rows through separate execute-only, tenant-bound database credentials. See direct interface scope; HTTP callers cannot choose SQL/routines.

In addition to the process overload guard, authenticated requests use a database- coordinated service/tenant quota (API_SERVICE_RATE_PER_MINUTE, default 120). Multiple credentials/workers for one service/tenant share it, so key rotation does not reset the limit. Rejection returns 429 SERVICE_RATE_LIMITED. The fixed-row counter avoids unbounded per-minute history. Invalid identities still hit the process/gateway admission guards. This HTTPS quota does not throttle MySQL protocol connections.