01Read the contract
Inventory what the Merchant API can do from the OpenAPI file alone, and write down what the contract does not tell you.
Start with the contract, not the requirements
Read the OpenAPI file first, alone, before you open the requirements. If you read the requirements first, you will read the contract looking for confirmation. Read the contract cold and you will see what it actually promises.
Read it in this order:
- Paths. What resources exist, what operations each supports. Write them as verbs: create a payment, capture a payment, refund, retrieve, list.
- Schemas. For each resource, the fields, their types, and which are required. Pay attention to types that look too loose for the domain: a
numberwhere you expect an integer, astringwhere you expect an enum. - Status values. A payment has a lifecycle. The contract encodes it as an enum. Copy the enum out and ask whether it is complete for a card payment that can go through 3-D Secure.
- Errors. Find every documented status code and every error code. The list is usually shorter than what the API actually returns.
- Retry safety. Look for an idempotency mechanism. Is it required? What happens when a key is reused?
- Webhooks. Which events exist, what a merchant receives, and how they can verify that the call is genuine.
What to write down
Two lists. The first is the inventory: what the contract says. The second is the more valuable one: what the contract does not say but a developer will need on day one. Typical entries in the second list are pagination mechanics, rate limits, what a 409 means, and how long an authorization stays capturable.
Do not look for gaps against requirements yet. You do not have them. Just note what is unspecified.
Your task
Build a one-page capability inventory: every endpoint, what it creates or returns, the status values a payment can take, the error codes the contract documents, and how a merchant is meant to retry safely. Then write a second list: five things a developer would need to know that the contract does not say.
Evidence for this step
Read it in the page or download it and open it in your own tools.
Platform artifact openapi.yaml 470 lines download show
openapi: 3.1.0
info:
title: Northline Pay Merchant API
version: "1.4.0"
description: |
Create payments on behalf of buyers, capture, refund, and list them.
All amounts are expressed in the payment currency.
Authenticate with your secret API key as a bearer token.
contact:
name: Northline Pay API team
email: api@northlinepay.example
servers:
- url: https://api.sandbox.northlinepay.example/v1
description: Sandbox
- url: https://api.northlinepay.example/v1
description: Production
security:
- apiKey: []
paths:
/payments:
post:
operationId: createPayment
summary: Create a payment
description: |
Creates a payment for a buyer. With `capture_method: automatic` the
payment is authorized and captured in one call. With `manual`, call
the capture endpoint later.
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreatePaymentRequest"
responses:
"201":
description: Payment created
headers:
Northline-Request-Id:
$ref: "#/components/headers/RequestId"
content:
application/json:
schema:
$ref: "#/components/schemas/Payment"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"402":
description: The payment method was declined
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
get:
operationId: listPayments
summary: List payments
description: Returns the merchant's payments, most recent first.
parameters:
- name: limit
in: query
description: Number of payments to return.
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: status
in: query
schema:
$ref: "#/components/schemas/PaymentStatus"
responses:
"200":
description: A page of payments
content:
application/json:
schema:
type: object
required: [object, data, has_more]
properties:
object:
type: string
const: list
data:
type: array
items:
$ref: "#/components/schemas/Payment"
has_more:
type: boolean
"401":
$ref: "#/components/responses/Unauthorized"
/payments/{payment_id}:
get:
operationId: getPayment
summary: Retrieve a payment
parameters:
- $ref: "#/components/parameters/PaymentId"
responses:
"200":
description: The payment
content:
application/json:
schema:
$ref: "#/components/schemas/Payment"
"404":
$ref: "#/components/responses/NotFound"
/payments/{payment_id}/capture:
post:
operationId: capturePayment
summary: Capture an authorized payment
parameters:
- $ref: "#/components/parameters/PaymentId"
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
content:
application/json:
schema:
type: object
properties:
amount:
type: number
description: Amount to capture. Defaults to the authorized amount.
responses:
"200":
description: The captured payment
content:
application/json:
schema:
$ref: "#/components/schemas/Payment"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: The payment is not in a capturable state
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
/refunds:
post:
operationId: createRefund
summary: Refund a captured payment
description: Refunds all or part of a captured payment.
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [payment_id]
properties:
payment_id:
type: string
amount:
type: number
description: Amount to refund. Defaults to the full captured amount.
reason:
type: string
enum: [requested_by_customer, duplicate, fraudulent]
responses:
"201":
description: Refund created
content:
application/json:
schema:
$ref: "#/components/schemas/Refund"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
/refunds/{refund_id}:
get:
operationId: getRefund
summary: Retrieve a refund
parameters:
- name: refund_id
in: path
required: true
schema:
type: string
responses:
"200":
description: The refund
content:
application/json:
schema:
$ref: "#/components/schemas/Refund"
"404":
$ref: "#/components/responses/NotFound"
webhooks:
payment.authorized:
post:
summary: A payment was authorized
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEvent"
responses:
"200":
description: Acknowledge with any 2xx status
payment.captured:
post:
summary: A payment was captured
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEvent"
responses:
"200":
description: Acknowledge with any 2xx status
payment.failed:
post:
summary: A payment failed or was declined
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEvent"
responses:
"200":
description: Acknowledge with any 2xx status
refund.succeeded:
post:
summary: A refund was completed
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/WebhookEvent"
responses:
"200":
description: Acknowledge with any 2xx status
components:
securitySchemes:
apiKey:
type: http
scheme: bearer
description: Your secret key, `sk_test_...` in sandbox and `sk_live_...` in production.
parameters:
PaymentId:
name: payment_id
in: path
required: true
schema:
type: string
pattern: "^pay_[A-Za-z0-9]{16}$"
IdempotencyKey:
name: Idempotency-Key
in: header
required: false
description: A unique key you generate to safely retry the request.
schema:
type: string
maxLength: 64
headers:
RequestId:
description: Identifier of this request, for support tickets.
schema:
type: string
responses:
BadRequest:
description: The request was invalid
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Unauthorized:
description: The API key is missing or invalid
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
NotFound:
description: No such resource for this merchant
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
schemas:
PaymentStatus:
type: string
enum: [pending, authorized, captured, failed, canceled]
CreatePaymentRequest:
type: object
required: [amount, currency, payment_method]
properties:
amount:
type: number
description: The payment amount.
currency:
type: string
description: Three-letter currency code.
capture_method:
type: string
enum: [automatic, manual]
default: automatic
payment_method:
$ref: "#/components/schemas/PaymentMethodInput"
customer:
type: object
properties:
email:
type: string
format: email
description:
type: string
maxLength: 200
split:
type: array
description: How the amount is distributed across seller accounts.
items:
type: object
required: [account_id, amount]
properties:
account_id:
type: string
amount:
type: number
metadata:
type: object
additionalProperties:
type: string
PaymentMethodInput:
type: object
required: [type, token]
properties:
type:
type: string
enum: [card, sepa_debit, interac]
token:
type: string
description: A payment method token created with Northline.js.
Payment:
type: object
required: [id, object, status, amount, currency, capture_method, payment_method, created_at]
properties:
id:
type: string
object:
type: string
const: payment
status:
$ref: "#/components/schemas/PaymentStatus"
amount:
type: number
currency:
type: string
capture_method:
type: string
enum: [automatic, manual]
payment_method:
type: object
properties:
type:
type: string
card:
type: object
properties:
brand:
type: string
last4:
type: string
exp_month:
type: integer
exp_year:
type: integer
customer:
type: object
properties:
email:
type: string
split:
type: array
items:
type: object
properties:
account_id:
type: string
amount:
type: number
failure_code:
type: string
failure_message:
type: string
metadata:
type: object
additionalProperties:
type: string
created_at:
type: string
format: date-time
Refund:
type: object
required: [id, object, payment_id, amount, currency, status, created_at]
properties:
id:
type: string
object:
type: string
const: refund
payment_id:
type: string
amount:
type: number
currency:
type: string
status:
type: string
enum: [pending, succeeded, failed]
reason:
type: string
created_at:
type: string
format: date-time
WebhookEvent:
type: object
required: [id, type, created_at, data]
properties:
id:
type: string
type:
type: string
created_at:
type: string
format: date-time
data:
type: object
description: The payment or refund resource, as returned by the API.
Error:
type: object
required: [type, code, message]
properties:
type:
type: string
enum: [invalid_request_error, authentication_error, card_error, api_error]
code:
type: string
description: |
One of: invalid_request, authentication_failed, payment_not_found,
payment_not_capturable, card_declined, rate_limited.
message:
type: string
param:
type: string
description: The request field the error refers to, when applicable.