Skip to main content

Sfey REST API v3 Design Principles

This document defines the REST design principles governing Sfey REST API v3 surfaces that are documented under reference/v3.0/. The TPS AFC API (reference/v3.0/tps-afc-api.yaml) is the normative example: production base URL https://tps.sfey.com/afc-api/v3 (see servers in that spec). All endpoints and OpenAPI specifications in the versioned reference must conform to these rules.


1. Resource Model and URL Structure

1.1 Base Path

Base path structure is: /{api}/v{versionMajor}/… (for example afc-api as the {api} segment for the TPS AFC API).

Currently, the following v3.0 OpenAPI root documents exist under reference/v3.0/:

  • tps-afc-api.yaml — TPS AFC API (transaction processing and card management for AFC integration; resource paths below live under this document’s servers URL).
  • tps-afc-callback-api.yaml — TPS AFC Callback API (webhook contract implemented by the AFC backend); path layout differs from the REST API and is not covered in detail here.

This document covers requirements for API major version 3 as implemented by tps-afc-api.yaml.

Every subresource is accessed under the company namespace: /company/{companyId}/….

The TPS AFC API exposes the following resource types:

ResourceParent resourceUnique IDPath namespaceComment
CompanyCompany ID/company/{companyId}Namespace only; company is not a standalone resource in this spec—only its subresources are addressable
TransactionCompanytransactionUuid (UUID)/company/{companyId}/transaction, /company/{companyId}/transaction/{transactionUuid}Payments, refunds, and debt recoveries; operation chosen via transactionType in the body (POST) or in the returned resource
CardCompanyICC certificate hash/company/{companyId}/card/by-icchash/{iccHash}Primary AFC card lookup: iccHash = Base64Url(SHA256(EMV Tag 9F46))
CardCompany(search)/company/{companyId}/cardCard search by PAN fragments and optional filters (GET)
EventCompanyiccHash (ICC hash)/company/{companyId}/eventDeny-list events for a card; iccHash is a required query parameter (ICC hash), see tps-afc-api.yaml CardId

1.2 Path Naming

RuleConventionExample
Segment casinglowercase kebab-case/by-icchash
Resource namessingular nouns…/transaction, …/card, …/event
Sub-resourcesunder company/company/{companyId}/transaction/{transactionUuid}
No trailing slashespaths never end with /…/card/{iccHash}…/card/{iccHash}/
No verbs in pathsactions expressed via HTTP methodsPOST …/transaction not …/create-transaction
No file extensionscontent negotiation via Accept…/transaction not …/transaction.json

1.3 Path Parameters

  • Use camelCase for parameter names: companyId, transactionUuid, iccHash.
  • Every path parameter must be required: true.
  • Type and format must be explicit (e.g. integer/int64, string/uuid).
  • Parameters that are identifiers should use the most specific type available: companyIdinteger/int64, transactionUuidstring/uuid, iccHashstring.

2. HTTP Methods

MethodSemanticsRequest bodySafeIdempotent
GETRetrieve a resourceNoYesYes
POSTCreate a resource / actionYesNoNo*
PUTFull replace of a resourceYesNoYes
DELETERemove a resourceNoNoYes

* POST createTransaction relies on the client transaction id in transactionData.uuid for idempotent behaviour via 409 Conflict on duplicates (see §6).

Method Selection Rules

  1. Retrieval — always GET, never POST-to-query. Exception: search endpoints that would exceed URL length limits may use POST with a request body, but GET with query parameters is preferred when feasible. Also, search endpoints containing secret information in their parameters should use POST.
  2. CreationPOST to the collection resource (POST /company/{companyId}/transaction in tps-afc-api.yaml). The request body uses CreateTransactionRequest; the client-supplied transaction id is transactionData.uuid in components/schemas/TransactionData.yaml (the operation text in tps-afc-api.yaml refers to the same value as the idempotency key).
  3. Full replacement — PUT is not defined on tps-afc-api.yaml paths; reserved if added later.
  4. Deletion — DELETE is not defined on tps-afc-api.yaml paths; reserved if added later.

3. Request Conventions

3.1 Content Type

  • Request bodies: application/json
  • No multipart/form-data or application/x-www-form-urlencoded

3.2 Request Body Rules

  • GET and DELETE requests must not have a body.
  • POST and PUT requests must have a body (even if minimal).
  • All fields use camelCase naming.
  • Required fields must be marked as required in the schema.
  • Optional fields should have sensible defaults documented in the description.

3.3 Query Parameters

  • Used for filtering, sorting, and pagination on GET collection endpoints.
  • Use camelCase naming.
  • Array-valued parameters use repeated keys where applicable, e.g. ?actionCodes=A&actionCodes=B for the actionCodes array on listTransactions in tps-afc-api.yaml.
  • Date/time parameters use ISO 8601 format and should support time zone information: 2025-01-20T22:00:00Z.

4. Response Conventions

4.1 Content Type

  • All responses: application/json

4.2 Success Status Codes

CodeWhen to use
200GET success (list, get-by-id, and success responses with a body)
201POST createTransaction — resource created
204Not used in tps-afc-api.yaml (no success response without a body)

4.3 Response Body Rules

  • 200 and 201 responses return a JSON body as defined for each operation in tps-afc-api.yaml (see components/responses/*).
  • Field naming: camelCase, matching request conventions.
  • Null fields should be omitted rather than sent as null, unless the distinction between absent and null carries semantic meaning.
  • Timestamps use ISO 8601 with UTC offset: 2025-06-15T14:30:00Z.
  • Monetary amounts use integer/int32 (minor unit, e.g. öre) with an accompanying currencyCodeNum field (integer/int32, ISO 4217 numeric code), as stated in tps-afc-api.yaml info.description.

5. Error Handling

5.1 Error Status Codes

CodeMeaningWhen to use
400Bad RequestMalformed JSON, missing required fields, constraint violations
401UnauthorizedMissing or invalid authentication credentials
403ForbiddenAuthenticated but insufficient permissions
404Not FoundResource identified by path parameters does not exist
409ConflictDuplicate client transaction id (transactionData.uuid) for create, or other conflict (see §6)
422Unprocessable EntitySyntactically valid request that fails business validation
500Internal Server ErrorUnexpected server failure (should be minimised)
502Bad GatewayUpstream service returned an invalid response
503Service UnavailableServer temporarily unable to handle requests
504Gateway TimeoutUpstream service did not respond in time

5.2 Error Response Schema

In tps-afc-api.yaml, most error responses reference components/responses/ErrorResponse, which uses the shared schema components/schemas/ErrorResponse.yaml:

# reference/v3.0/components/schemas/ErrorResponse.yaml
type: object
description: Error message
properties:
systemCode:
type: integer
format: int32
description: Error code
systemCodeDescription:
type: string
description: Error description

401 uses AuthenticationErrorResponse (adds a WWW-Authenticate header) with the same JSON body schema. Client and server errors are grouped in the spec with '4XX' and '5XX' response entries that both reference this ErrorResponse pattern.

5.3 Error Code Ranges

RangeOwnerExample
1000–1999Validation errors1001: Missing required field
2000–2999Authentication/authz2001: Token expired
3000–3999Resource errors3001: Transaction not found
4000–4999Business-rule errors4001: Unprocessable tap (AFC integration compat)
5000–5999Upstream/infra5001: Card network timeout

6. Idempotency

POST /company/{companyId}/transaction uses the create request schema components/schemas/CreateTransactionRequest.yaml. The client-supplied transaction identifier is transactionData.uuid (string / uuid in TransactionData.yaml). The createTransaction description in tps-afc-api.yaml states that the same UUID must not be reused for a different transaction: an existing transaction with that identifier results in 409 Conflict.

There is no PUT or DELETE on the paths in tps-afc-api.yaml. GET methods are safe and idempotent by HTTP semantics.


7. Pagination

tps-afc-api.yaml uses two collection patterns:

7.1 Transaction list (GET /company/{companyId}/transaction)

Query parameters (see listTransactions in tps-afc-api.yaml):

ParameterTypeRequiredDescription
pageinteger (int32)yesPage number (zero-based)
sizeinteger (int32)yesPage size

Other filters (startDateTime, endDateTime, panToken, transactionUuid, transactionType, actionCodes, clearingSequenceNumber, direction, sort) apply as documented on the operation. Note: sort is marked deprecated in the spec in favour of the comma-separated fields described on that parameter.

7.2 Card search (GET /company/{companyId}/card)

Offset-based pagination:

ParameterTypeDefaultConstraintsDescription
offsetinteger (int32)0minimum 0Number of items to skip
limitinteger (int32)201–100Max items to return

7.3 Paginated response envelope

List responses (ListTransactionsResponse, ListCardsResponse, ListEventsResponse) share the same shape: an array data plus a page object defined by components/schemas/PageData.yaml:

# PageData — summary
properties:
pageNumber: # current page (0-based)
pageSize:
hasNext:
filters: # echo of request filters (object, additionalProperties)

This replaces older content / offset / limit / hasMore naming used in earlier drafts.


8. Filtering and Sorting

8.1 Card search (GET /company/{companyId}/card)

  • PAN fragments: panFirstDigits, panLastDigits (optional; any combination allowed per spec).
  • Card expiry filter: expiryYearMonth (ISO year-month, YYYY-MM).
  • Created range: createdStart, createdEnd (ISO 8601 date-time, UTC).
  • Sorting: sort (field name, e.g. created) and direction (ASC / DESC, default DESC per tps-afc-api.yaml parameter definitions).

8.2 Transaction list (GET /company/{companyId}/transaction)

  • Required window: startDateTime, endDateTime (ISO 8601 strings as described on the operation).
  • Optional filters: panToken, transactionUuid, transactionType, actionCodes, clearingSequenceNumber.
  • Sorting: optional direction (ASC / DESC); sort is deprecated — see the operation for the comma-separated sort fields array.

8.3 General rules

  • Date range filters for card search use a Start / End suffix on the field name (createdStart, createdEnd).

9. Versioning

  • The version is embedded in the URL path: /afc-api/v3/… (see servers in tps-afc-api.yaml).
  • Major version increments indicate breaking changes.
  • Non-breaking additions (new optional fields, new endpoints) do not require a version bump.
  • The info.version field follows semantic versioning and may include pre-release labels for work in progress (for example 3.0.0-alpha1 in tps-afc-api.yaml).

10. Security

  • All endpoints require OAuth 2.0 Bearer tokens.
  • The token is passed in the Authorization: Bearer <token> header.
  • The OpenAPI document (tps-afc-api.yaml) declares components.securitySchemes.bearerAuth as type oauth2 with the clientCredentials flow (tokenUrl: https://tps.sfey.com/oauth/token, scopes: {}) and applies it globally via security: [ { bearerAuth: [] } ].
  • No API keys, basic auth, or cookie-based auth.

11. Naming Conventions Summary

ElementConventionExample
Path segmentskebab-case/by-icchash
Path parameterscamelCase{companyId}
Query parameterscamelCase?panFirstDigits=4111
Request/response fieldscamelCasetransactionUuid
Schema namesPascalCaseCreateTransactionRequest
Enum valuesUPPER_SNAKETRANSIT_VALIDATION_PRICE
Header namesStandard HTTPAuthorization, Content-Type

12. OpenAPI Specification File Structure

12.1 Directory Layout

The reference/ directory is the root for all OpenAPI specifications. Its structure mirrors the OpenAPI document model so that file paths are predictable from the $ref they resolve.

reference/
├── old/ # legacy / pre-versioned specs (flat layout)
| └── <legacy-spec>.yaml
└── v<major>.<minor>/ # e.g. v3.0 — versioned API surface
├── tps-afc-api.yaml # TPS AFC API (TPS, AFC integration)
├── tps-afc-callback-api.yaml # AFC backend webhook contract (separate base URL)
└── components/
└── schemas/
├── CreateTransactionRequest.yaml
├── ErrorResponse.yaml
├── PageData.yaml
├── TransactionData.yaml
├── CardData.yaml
└── … # other domain and response payloads

12.2 Version Directories

RuleConventionExample
Directory namev<major>.<minor>v3.0
One directory per API versionA new minor/major version gets its own directoryv3.0/, v3.1/
Legacy specsSpecs predating the versioned layout remain flat in reference/old/tps-api-v2.yaml

The version segment in the directory name matches the info.version major.minor of the contained specifications (e.g. v3.0/ contains specs with info.version: 3.0.x).

12.3 Root API Specification Files

Each root document lives directly inside its version directory. Naming rules:

RuleConventionExample
File naming<solution-name>-<api-name>.yamltps-afc-api.yaml, tps-afc-callback-api.yaml
Casinglowercase kebab-casetps-afc-api.yaml ✓, TpsAfcApi.yaml
Extension.yaml (not .yml)tps-afc-api.yaml
No version in filenameThe version directory provides this contexttps-afc-api.yaml ✓, tps-afc-api-v3.yaml

A root document contains the full OpenAPI skeleton: openapi, info, servers, tags, security, paths (or webhooks), and components. Shared payload and domain schemas live in components/schemas/*.yaml and are referenced with direct relative $refs from components/requestBodies, components/responses, and from other schema files (see §12.5 — no pass-through entries under components.schemas for file-backed shared types).

No inline request bodies or responses. In tps-afc-api.yaml, operations reference components/requestBodies/* and components/responses/* via $ref rather than inlining full requestBody / responses objects under each path. Success and error response payloads typically $ref into components/schemas/*.yaml files (for example CreateTransactionRequest./components/schemas/CreateTransactionRequest.yaml).

12.4 Component Schema Files

Shared schemas (any type referenced from more than one place—another schema file, another components/* entry, or more than one operation—or intended for reuse) must live in a dedicated file under components/schemas/ inside the version directory. The file root is the schema object (no SchemaName: wrapper key at the top of the file).

RuleConventionExample
One schema per fileEach file defines a single top-level type: object (or enum)CardData.yaml
File namingPascalCase, matching the schema nameTransactionData.yaml, DeviceData.yaml
No wrapping keyThe file root is the schema object — no CardData: wrappertype: object at line 1
Local sub-schemasUse $defs (JSON Schema) for types scoped to a single fileTripData.yaml$defs.TripStopPoint
Internal $refReference local sub-schemas as #/$defs/<Name>$ref: '#/$defs/Coordinate'

When to extract: In addition to the shared-schema rule above, a schema may be extracted to its own file when it is complex enough that inlining it harms readability of the root document—even if it is only referenced once—provided references to it remain direct file $refs as described in §12.5 and §12.6.

12.5 Operation payloads vs shared domain schemas

Direct references only. Any consumer of a shared schema must $ref the YAML file with a relative path (for example './components/schemas/CardData.yaml' from the root OpenAPI document, or './CardData.yaml' from another file in components/schemas/). Do not add entries under the root document’s components.schemas whose sole purpose is to point at an external file (a pass-through or “re-export” such as SchemaName: { $ref: './components/schemas/SchemaName.yaml' }). That pattern duplicates the name, obscures the real location in tooling, and is not allowed for shared types. Prefer wiring requestBodies, responses, and other schema files straight to the file under components/schemas/.

The root document may still use components.schemas for inline fragments that are not extracted (rare) or for constructs that must remain in-document; it must not use that map as an indirection layer for file-backed shared schemas.

tps-afc-api.yaml and sibling root specs combine inline root components with external schema files as follows:

  1. components/parameters, components/requestBodies, and components/responses are declared in the root OpenAPI document and referenced from each operation via $ref (for example #/components/requestBodies/CreateTransactionRequest).

  2. Payload structures for requests and responses are defined in components/schemas/*.yaml and referenced directly from those components (for example the request body schema $ref: './components/schemas/CreateTransactionRequest.yaml'). Shared domain types (TransactionData.yaml, CardData.yaml, DeviceData.yaml, …) compose each other with direct relative $refs between files under components/schemas/.

  3. Reuse — The same schema file may be referenced from more than one components/responses/* entry or from several payload files; each reference is a direct path to the file, not via an intermediate name under components.schemas.

LayerWhere it livesExample
HTTP componentsRoot spec components/*requestBodies/CreateTransactionRequest
Top-level payloadcomponents/schemas/<Name>.yamlCreateTransactionRequest.yaml
Domain objectscomponents/schemas/*.yamlTransactionData.yaml

12.6 $ref Usage

ScopeSyntaxExample
Shared schema from root specRelative path to components/schemas/$ref: './components/schemas/ErrorResponse.yaml' (preferred; do not add a components.schemas alias that only repeats this $ref)
Shared schema from another file in components/schemas/Relative path to sibling or nested file$ref: './CardData.yaml', $ref: './DeviceKey.yaml'
Same document — request body#/components/requestBodies/<Name>$ref: '#/components/requestBodies/CreateTransactionRequest'
Same document — response#/components/responses/<Name>$ref: '#/components/responses/AuthenticationErrorResponse'
Sub-schema within external fileRelative path + JSON Pointer$ref: './components/schemas/TripData.yaml#/$defs/Coordinate'
Within an extracted file#/$defs/<Name>$ref: '#/$defs/TripStopPoint'

Avoid #/components/schemas/<Name> when <Name> is only a wrapper around a single $ref to a file under components/schemas/; use the file path from the referencing context instead.

Circular $ref chains are not allowed. Every reference path must be resolvable with standard JSON Reference / OpenAPI 3.1 resolution.

12.7 Request Bodies

Every operation that accepts a request body must declare it in components/requestBodies and reference it via $ref. Inline request bodies under paths or webhooks are not permitted.

RuleConventionExample
NamingPascalCase; key under components/requestBodiesCreateTransactionRequest
Content typeapplication/json
requiredSet explicitly on the request body componentrequired: true
Schema $refRelative path to YAML under components/schemas/$ref: './components/schemas/CreateTransactionRequest.yaml'

12.8 Responses

Every response declared on an operation must be defined in components/responses and referenced via $ref. Inline response definitions under paths or webhooks are not permitted.

RuleConventionExample
Success namingPascalCase, outcome-orientedCreateTransactionResponse, ListCardsResponse
Error namingPascalCase; dedicated 401 response where neededAuthenticationErrorResponse, ErrorResponse
Content typeapplication/json for JSON bodies
Error schemaJSON body $ref: './components/schemas/ErrorResponse.yaml' (401 adds WWW-Authenticate header)
Aggregatestps-afc-api.yaml groups client/server errors as '4XX' / '5XX' entries
ReuseDefine common responses once under components/responses and reuse across operations

12.9 Future Component Types

The components/ directory may be extended with additional OpenAPI component types as the spec evolves. New subdirectories follow the same pattern:

components/
├── schemas/ # reusable schemas
├── parameters/ # reusable path/query/header parameters (already in use in root specs)
├── responses/ # reusable response definitions (may be extracted to files in the future)
├── requestBodies/ # reusable request body definitions (may be extracted to files in the future)
└── examples/ # reusable examples

Each subdirectory name matches the corresponding key under components in the OpenAPI document. In reference/v3.0/tps-afc-api.yaml, reusable payload types live under components/schemas/ as individual YAML files and are referenced directly by path from root components and from each other; components/parameters, components/requestBodies, and components/responses are defined in the root document and point at those files without intermediate components.schemas aliases. Additional component types may be split into separate files under other components/* keys when reuse across specs grows.


License

Copyright (c) 2025-2026 Sfey Pay. All rights reserved.

This software is proprietary and forms part of the Sfey Intelligence System (SIS). It is licensed exclusively for internal use by authorized Sfey personnel and systems operating within the AXON (Agentic Execution & Observability Network) infrastructure.

You may not, without prior written authorization from Sfey Pay:

  • Copy, reproduce, or distribute this software or any portion of it.
  • Modify, adapt, or create derivative works based on this software.
  • Reverse-engineer, decompile, or disassemble any part of this software.
  • Sublicense, lease, or lend access to any third party.

Unauthorized use, reproduction, or distribution is strictly prohibited and may result in civil liability and criminal penalties under applicable intellectual property law.

For licensing inquiries contact [email protected].