Template: API Contract Document (REST & GraphQL)

Section 1 – API Metadata (Rebuilt)

The metadata section provides context about the API contract before diving into endpoints. It ensures that anyone consuming the API — whether internal developers, QA, or external partners — immediately understands the scope, ownership, authentication, and environments. A well-written metadata section prevents confusion, ensures accountability, and acts as a quick reference for governance.


Metadata Table

FieldDescriptionExample (REST – SaaS App)Example (GraphQL – B2C App)
API NameHuman-readable name of the APIUser Onboarding APIBooking Service Schema
VersionSemantic version number (major.minor.patch)v1.2.02025-08 schema
OwnerSquad/team responsible for API maintenanceBackend Team – SaaS Squad AMobile API Team
Point of ContactEscalation contact (Slack channel, email)#api-squad-a, api-support@company.com#mobile-api, mobile-support@company.com
Base URL – ProductionEndpoint for production environmenthttps://api.saasapp.com/v1https://api.ecoclean.com/graphql
Base URL – StagingEndpoint for testing environmenthttps://staging.api.saasapp.com/v1https://staging.api.ecoclean.com/graphql
Base URL – DevelopmentEndpoint for dev sandboxhttp://localhost:8080/api/v1http://localhost:4000/graphql
Auth MechanismAuthentication standard usedOAuth2 (Bearer Token)Firebase Auth (JWT)
Scopes / RolesRole-based access or OAuth scopes requiredusers.read, users.writebooking:read, booking:write
Rate LimitsAllowed requests per client100 requests/min60 queries/min
Timeout PolicyAPI request timeout (ms)30,000 ms20,000 ms
Deprecation PolicyRules for version retirement90-day notice, /v1 sunset in 2026Schema changes flagged in changelog
Change TrackingWhere changes are loggedADR Doc 12ADR Doc 12
Monitoring & LoggingTools and dashboards usedDatadog, KibanaGrafana, ELK Stack
Error Logging ContactWho monitors errorsDevOps Squad AMobile DevOps Squad

Section 2 – Endpoints / Operations (Rebuilt)

Purpose of This Section

The Endpoints/Operations section defines the actual API contract. Unlike generic notes, this must include URLs, methods, authentication rules, required parameters, request/response bodies, and error codes. For REST APIs, endpoints are URI + HTTP method–driven; for GraphQL APIs, operations are schema-based queries and mutations. This section ensures frontend, backend, QA, and third-party consumers all speak the same language before development begins.


Table A – REST Endpoints (20 rows)

EndpointMethodDescriptionAuth RequiredRequest ParamsRequest Body ExampleSuccess Response (200)Error Codes
/usersPOSTCreate new userYes (Bearer)None{ "name": "John", "email": "john@x.com", "password": "123456" }{ "id": 101, "name": "John", "email": "john@x.com" }400 Invalid Email, 409 Conflict
/users/{id}GETGet user profileYesPath: idNone{ "id": 101, "name": "John", "status": "active" }404 Not Found
/users/{id}PUTUpdate user profileYesPath: id{ "name": "John D", "status": "inactive" }{ "id": 101, "updatedAt": "2025-09-18T10:00:00Z" }400 Invalid Input
/users/{id}DELETEDelete userAdmin OnlyPath: idNone{ "message": "User deleted" }403 Forbidden, 404 Not Found
/auth/loginPOSTAuthenticate userNoNone{ "email": "john@x.com", "password": "123456" }{ "token": "jwt-token", "expiresIn": 3600 }401 Unauthorized
/auth/refreshPOSTRefresh access tokenYes (Refresh Token)None{ "refreshToken": "abcd1234" }{ "token": "new-jwt-token" }401 Invalid Token
/projectsGETList projectsYesQuery: page, limitNone{ "data": [ { "id": 1, "name": "Alpha" } ], "meta": { "page": 1 } }401 Unauthorized
/projectsPOSTCreate projectYesNone{ "name": "Project Alpha" }{ "id": 201, "name": "Project Alpha" }400 Bad Request
/projects/{id}GETGet project detailYesPath: idNone{ "id": 201, "name": "Alpha", "status": "active" }404 Not Found
/projects/{id}PUTUpdate projectYesPath: id{ "status": "archived" }{ "id": 201, "status": "archived" }400 Invalid Input
/tasksGETList tasksYesQuery: projectId, statusNone{ "data": [ { "id": 301, "title": "Design UI" } ] }401 Unauthorized
/tasksPOSTCreate taskYesNone{ "title": "Design UI", "projectId": 201 }{ "id": 301, "title": "Design UI" }400 Bad Request
/tasks/{id}GETGet task detailYesPath: idNone{ "id": 301, "title": "Design UI", "status": "open" }404 Not Found
/tasks/{id}PUTUpdate taskYesPath: id{ "status": "done" }{ "id": 301, "status": "done" }400 Invalid Input
/tasks/{id}DELETEDelete taskYesPath: idNone{ "message": "Task deleted" }404 Not Found
/reportsGETGenerate reportYesQuery: from, toNone{ "reportId": "rpt123", "status": "ready" }400 Invalid Dates
/reports/{id}GETDownload reportYesPath: idNoneFile stream (PDF/CSV)404 Report Not Found
/notificationsGETFetch notificationsYesQuery: limitNone{ "data": [ { "id": 401, "text": "Task due soon" } ] }401 Unauthorized
/notifications/{id}PUTMark notification readYesPath: idNone{ "id": 401, "status": "read" }404 Not Found
/settings/profilePUTUpdate user settingsYesNone{ "timezone": "UTC", "language": "en" }{ "id": 101, "updatedAt": "..." }400 Invalid Input

Table B – GraphQL Operations (10 rows)

Operation TypeOperation NameDescriptionExample Query/MutationSuccess ResponseError Cases
QuerygetUserFetch a user by IDquery { getUser(id: "101") { id name email } }{ "data": { "getUser": { "id": "101", "name": "John" } } }User not found
MutationcreateUserCreate a new usermutation { createUser(name:"John", email:"john@x.com") { id name } }{ "data": { "createUser": { "id": "101", "name": "John" } } }Duplicate email
MutationupdateUserUpdate user detailsmutation { updateUser(id:"101", status:"inactive") { id status } }{ "data": { "updateUser": { "id": "101", "status": "inactive" } } }Invalid input
QuerylistProjectsList all projectsquery { listProjects(limit:10) { id name } }{ "data": { "listProjects": [ { "id": "201", "name": "Alpha" } ] } }Unauthorized
MutationcreateProjectAdd a new projectmutation { createProject(name:"Alpha") { id name } }{ "data": { "createProject": { "id": "201", "name": "Alpha" } } }Bad request
QuerygetProjectGet project detailquery { getProject(id:"201") { id name status } }{ "data": { "getProject": { "id": "201", "name": "Alpha" } } }Not found
MutationupdateProjectUpdate project detailmutation { updateProject(id:"201", status:"archived") { id status } }{ "data": { "updateProject": { "id": "201", "status": "archived" } } }Invalid status
QuerylistTasksFetch tasks for a projectquery { listTasks(projectId:"201") { id title status } }{ "data": { "listTasks": [ { "id":"301", "title":"UI" } ] } }Unauthorized
MutationcreateTaskAdd new taskmutation { createTask(title:"UI", projectId:"201") { id title } }{ "data": { "createTask": { "id":"301", "title":"UI" } } }Missing projectId
MutationupdateTaskUpdate task statusmutation { updateTask(id:"301", status:"done") { id status } }{ "data": { "updateTask": { "id":"301", "status":"done" } } }Invalid taskId

Section 3 – Request/Response Standards (Rebuilt)

Purpose of This Section

Request/Response standards exist to enforce uniformity across APIs, so that frontend developers, QA engineers, and third-party integrators don’t have to guess what structure to expect from one endpoint to another. Without standards, APIs become inconsistent: some return raw objects, others wrap in data, some paginate differently, and error responses vary wildly. This creates friction, wasted time, and bugs. A proper specification establishes one way of doing things, covering pagination, filtering, sorting, success envelopes, and error handling. It ensures APIs are predictable, testable, and scalable across the product lifecycle.


Table A – REST Standards

AspectContractExample
HTTP MethodsUse correct verbs: GET (read), POST (create), PUT (update), DELETE (remove). PATCH for partial updates.PUT /users/{id} updates full profile; PATCH /users/{id} updates status only.
Content TypeAll requests and responses must use JSON with header Content-Type: application/json.POST /users{"name":"John"}
HeadersStandard headers: Authorization: Bearer <token>, Accept: application/json.curl -H "Authorization: Bearer token" -H "Accept: application/json"
PaginationCursor-based preferred; fallback to page/limit. Always return pagination metadata.GET /users?page=2&limit=20{ "data":[...], "meta":{"page":2,"limit":20,"total":55} }
FilteringFilters must be explicit query params; multiple filters allowed./users?status=active&role=admin
SortingUse query param sort with +/- prefix. Default ascending./projects?sort=-createdAt
SearchUse q param for free-text search./tasks?q=design
Success EnvelopeAlways wrap response in { "data":..., "meta":... }. meta optional.{ "data":{"id":101,"name":"John"} }
Error FormatUnified structure: { "error": { "code": <int>, "message": <string>, "details": <object> } }{ "error":{ "code":400,"message":"Invalid Email","details":{"field":"email"} } }
TimestampsAlways ISO 8601 UTC."createdAt":"2025-09-18T10:00:00Z"

Table B – GraphQL Standards

AspectContractExample
SchemaAll operations must be strongly typed with nullable vs. non-nullable explicitly defined.type User { id: ID! name: String! email: String }
Query NamingUse descriptive names; avoid generic like getData.getUser, listTasks
Mutation NamingUse imperative verbs: createX, updateX, deleteX.mutation { createTask(...) }
PaginationRelay-style cursor-based (edges, pageInfo) recommended.tasks(first:10, after:"cursor123"){ edges{node{id title}} pageInfo{hasNextPage}}
FilteringUse input objects for multiple filters.users(filter:{status:"active", role:"admin"}) { id name }
SortingSorting fields must be explicit in schema.users(sort:{field:"createdAt", order:DESC}) { id name }
Success EnvelopeAlways return under "data" key.{ "data": { "getUser": { "id":"101","name":"John"} } }
Error FormatAll errors under "errors" array, with message, path, extensions.{ "errors":[ { "message":"Invalid ID", "path":["getUser"], "extensions":{"code":"400"}} ] }
Auth ContextAuth tokens must be passed via headers; resolvers must enforce role-level access.Authorization: Bearer token
TimestampsISO 8601 UTC for all datetime fields."updatedAt":"2025-09-18T10:05:00Z"

Section 4 – Error Codes (Rebuilt)

Purpose of This Section

Error codes define how the API communicates problems to consumers. A weak error strategy leads to chaos: frontend developers can’t distinguish between a user mistake and a server crash, QA testers can’t automate edge cases, and third-party integrators waste time guessing what went wrong. A good error catalogue ensures that every error is predictable, consistent, and actionable. This section establishes both HTTP-level status codes and application-level error codes/messages that align with the request/response standards (see Section 3).


Table A – REST Error Codes (15 Rows)

HTTP CodeMeaningWhen UsedResponse Example
200 OKSuccessStandard for successful GET, PUT, DELETE{ "data": { "id":101,"status":"active" } }
201 CreatedResource createdAfter successful POST{ "data": { "id":201,"name":"Project Alpha" } }
202 AcceptedRequest accepted for async processingReport generation, background jobs{ "data": { "reportId":"rpt123","status":"processing" } }
204 No ContentSuccess with no bodyDELETE successEmpty body
400 Bad RequestInvalid input, missing paramsInvalid JSON, missing email{ "error": { "code":400,"message":"Missing email" } }
401 UnauthorizedMissing/invalid auth tokenNo Authorization header{ "error": { "code":401,"message":"Token missing/invalid" } }
403 ForbiddenValid token, insufficient rightsNon-admin trying to delete user{ "error": { "code":403,"message":"Insufficient role" } }
404 Not FoundResource doesn’t existInvalid id{ "error": { "code":404,"message":"User not found" } }
405 Method Not AllowedWrong HTTP verbPOST /reports/{id}{ "error": { "code":405,"message":"Method not allowed" } }
409 ConflictDuplicate or conflicting stateEmail already registered{ "error": { "code":409,"message":"Email exists" } }
410 GoneDeprecated resourceOld API version removed{ "error": { "code":410,"message":"This endpoint is deprecated" } }
422 Unprocessable EntitySemantic validation failurePassword too weak{ "error": { "code":422,"message":"Password must be 8+ chars" } }
429 Too Many RequestsRate limit exceeded101st request in a minute{ "error": { "code":429,"message":"Rate limit exceeded, retry in 60s" } }
500 Internal Server ErrorUnexpected server errorNull pointer exception{ "error": { "code":500,"message":"Unexpected server error" } }
503 Service UnavailableTemporary downtimeDB offline{ "error": { "code":503,"message":"Service temporarily unavailable" } }

Table B – GraphQL Error Handling (10 Rows)

Unlike REST, GraphQL always returns 200 OK at HTTP level — errors are inside an errors array. We standardize error codes with extensions.code.

Error CodeMeaningWhen UsedExample Response
GRAPHQL_VALIDATION_FAILEDInvalid query syntaxMisspelled field{ "errors":[{ "message":"Cannot query field foo","extensions":{"code":"GRAPHQL_VALIDATION_FAILED"}}] }
BAD_USER_INPUTInvalid argumentsemail missing in mutation{ "errors":[{ "message":"Email required","extensions":{"code":"BAD_USER_INPUT"}}] }
UNAUTHENTICATEDNo/invalid tokenMissing auth header{ "errors":[{ "message":"Not authenticated","extensions":{"code":"UNAUTHENTICATED"}}] }
FORBIDDENRole doesn’t allow operationUser without admin deleting project{ "errors":[{ "message":"Forbidden","extensions":{"code":"FORBIDDEN"}}] }
NOT_FOUNDEntity not foundNon-existent project ID{ "errors":[{ "message":"Project not found","extensions":{"code":"NOT_FOUND"}}] }
CONFLICTDuplicate or inconsistent stateDuplicate email{ "errors":[{ "message":"Email exists","extensions":{"code":"CONFLICT"}}] }
RATE_LIMITEDToo many requestsOver API quota{ "errors":[{ "message":"Rate limit exceeded","extensions":{"code":"RATE_LIMITED"}}] }
INTERNAL_SERVER_ERRORGeneric backend errorException in resolver{ "errors":[{ "message":"Unexpected error","extensions":{"code":"INTERNAL_SERVER_ERROR"}}] }
SERVICE_UNAVAILABLEDownstream service unavailableDB timeout{ "errors":[{ "message":"Database down","extensions":{"code":"SERVICE_UNAVAILABLE"}}] }
DEPRECATED_FIELDQuerying removed fieldLegacy schema{ "errors":[{ "message":"Field x deprecated","extensions":{"code":"DEPRECATED_FIELD"}}] }

Section 5 – Security & Auth (Rebuilt)

Purpose of This Section

Security is not an afterthought in API design — it is the contractual backbone that ensures only authorized consumers can access resources safely. Weak documentation here leads to massive risks: developers may skip proper checks, third-party integrators may leak tokens, and testers may miss critical abuse scenarios. A strong API specification must define authentication flows, authorization rules, data protection practices, and rate-limiting policies clearly enough that no consumer can claim ignorance. This section establishes both mechanical security requirements (OAuth2, JWT, HTTPS) and behavioral policies (roles, scopes, logging, rotation, rate limits).


Table A – REST Security & Auth Standards

AreaContractExample
Transport SecurityAll endpoints must use HTTPS with TLS 1.2+; no plaintext HTTP allowed.https://api.saasapp.com/v1/users
Auth MechanismDefault auth via OAuth2 (Authorization Code / Client Credentials) or JWT (Bearer token).Authorization: Bearer <jwt-token>
Token FormatJWT must include sub, iat, exp, and roles claims.{ "sub":"101", "exp":1695049200, "roles":["admin"] }
Token ExpiryAccess tokens expire in 1h; refresh tokens in 30d.Frontend refreshes automatically before expiry.
Refresh WorkflowToken refresh via /auth/refresh with refresh token.POST /auth/refresh { "refreshToken":"xyz" }
Scopes & RolesEndpoints require explicit scopes (e.g., users:read, projects:write)./users/{id} requires users:read.
Rate LimitingDefault: 100 requests/min per client. Higher tiers configurable.Exceeding limit → 429 error.
Replay ProtectionNonces or jti (JWT ID) required for sensitive actions.Prevents replay of /payments.
Logging RulesSensitive fields (passwords, tokens, PII) never logged in plaintext.Log only userId, endpoint, timestamp.
Audit TrailAll admin-level actions logged with userId, role, timestamp.Role change → audit entry in security_audit table.

Table B – GraphQL Security & Auth Standards

AreaContractExample
Transport SecurityAll queries and mutations over HTTPS/TLS only.POST https://api.ecoclean.com/graphql
Auth HeaderEvery request must include Authorization: Bearer <token>.curl -H "Authorization: Bearer jwt" ...
Field-Level AuthSensitive fields protected via resolver rules.User.email accessible only to self or admin.
Role EnforcementMutations validated by roles/scopes.createProject requires projects:write.
Rate LimitingLimit queries per client: 60/min. Complex queries count extra.listUsers(first:1000) = 10 ops cost.
Query Depth LimitMax query depth = 5 to prevent DoS via nested queries.users → projects → tasks → comments → likes.
Query Complexity LimitEach field weighted; queries > 100 points rejected.Weighted cost analysis.
Error SanitizationNever expose stack traces; return generic error + code.{ "errors":[{ "message":"Internal Error"}] }
Token Expiry & RefreshSame as REST (1h expiry, 30d refresh).Refresh via /auth/refresh.
Subscription SecurityWebSocket subscriptions must revalidate token on connection + periodically.connection_init must include JWT.

General Security Best Practices (Applies to REST + GraphQL)

  1. Principle of Least Privilege – Default roles (e.g., user) should have only minimum access; admin privileges require explicit assignment.
  2. CORS Policy – Allowed origins explicitly listed; no wildcard *.
  3. Input Validation – All incoming data validated at API boundary to prevent injection attacks.
  4. Data Masking – Sensitive outputs (tokens, partial card numbers, SSNs) masked before returning in API responses.
  5. Secrets Management – Keys and tokens stored in secure vaults (HashiCorp Vault, AWS Secrets Manager).
  6. Versioning Security – Old versions decommissioned with minimum 90-day notice. Security patches applied retroactively until sunset.
  7. Monitoring & Alerts – Auth failures, rate limit violations, and suspicious traffic patterns automatically flagged to security team.
  8. Penetration Testing – APIs undergo regular security tests before major releases.

Closing Note

This API Specification is designed as a living contract between teams — not a static document. It provides structure, consistency, and governance across REST and GraphQL APIs, but its true value lies in being actively maintained and adapted. Every product evolves: new endpoints are added, schemas change, authentication strategies harden, and integrations scale. As such, this document should always reflect the current reality of the API, not just the intent at launch.

Depending on your product type, architectural choices, or security requirements, sections of this template may need to be extended or refined. For example, a financial SaaS may require more rigorous audit trails, while a consumer mobile app may prioritize lightweight payloads and real-time subscriptions. What must remain non-negotiable is the discipline of consistency — ensuring that every API consumer can rely on predictable standards for requests, responses, errors, and security.

In practice, this means teams should treat the specification as part of their software lifecycle governance: version it alongside code, review it during design discussions, and update it whenever breaking or non-breaking changes are introduced. Used in this way, the API Specification stops being “just documentation” and becomes an operational safeguard — reducing bugs, avoiding miscommunication, and ensuring integrations succeed.