These rules apply to everything under portal/api-reference/@v2/ (path files in paths/, schemas in components/schemas/, the shared theme files they depend on). They were derived from a deep-dive documentation pass across Ping, Settings, Partner, Payment Sessions, Payment Methods, and Customers. Follow them for any future edit or new endpoint group in this folder, without being asked to repeat them.
The existing YAML frequently drifts from the real backend: dead routes that 404, renamed/removed/added fields, wrong response codes, wrong permission models, response shapes that don't match (e.g. a doc claiming "just an id" when the real endpoint returns the full resource). Never trust the current doc as ground truth. For every endpoint, read:
- The controller action (route, HTTP verb,
[RequireApiPermission]/[RequireMerchantToken]/[RequireAffiliateToken]attributes, every[SwaggerResponse]status code, the XML<summary>/<remarks>). - The request/response contract classes (field names, types, nullability,
<example>). - The FluentValidation validator (the real constraints — ranges, lengths, regex, required-when rules; C# XML doc comments on the DTO are sometimes stale compared to the validator).
- The mapping extension class (confirms exact field renames between internal and public DTOs, and default/fallback values).
- The
SwaggerExamplesclass (realistic example values to reuse verbatim). - Component tests, if present — they're the most reliable source for exact wire field names, status codes per scenario, and "old route/field is gone" migration facts.
When applying a rule to "an endpoint" or "an endpoint group," don't stop at the schema files a path file's $refs point to directly. Follow every $ref chain to its full depth: a field's schema can itself $ref another schema, which can $ref another, and so on. All of it is still that endpoint's documentation, and a rule applies there too. A violation several hops deep (for example inside a nested object field's own nested field) is just as much a miss as one in the top-level request/response schema. When sweeping for a rule across a group, build the full transitive closure of referenced files first, rather than checking only the schemas a path file names directly.
- One sentence per line, roughly 10 words. Don't write dense paragraphs.
- No semicolons. When the clause after where a semicolon would go states a new, coordinate fact, split it into its own sentence. When it's a pure logical conclusion of the first clause (restates/derives nothing new), delete it instead of turning it into a second sentence.
- No comma + coordinating conjunction (and/or/so/but) joining two independent clauses. If each side of the conjunction has its own subject and verb and could stand alone, split it into two sentences instead ("
Xrequires theYpermission, or the request returns a 403." → "Xrequires theYpermission. Otherwise, the request returns a 403."). If the comma instead joins two noun phrases, a compound predicate sharing one subject, or a serial list of three or more items, leave it — that's not this rule. When the second clause is a disjunctive alternative ("A, or B"), keep the alternative reading by leading the second sentence with "Alternatively," rather than flattening both into unconditional statements. Watch for a new sentence boundary landing on a lowercase field name when you split — rephrase with "Thefieldfield..." rather than starting the sentence with the bare identifier. - Never start a sentence with a lowercase letter, including a backtick-wrapped identifier. Not
`fieldName` is required.— rephrase as "ThefieldNamefield is required." Exception: short value pairs in a bullet list of`field`: `value`fragments are not sentences, don't need this treatment. - No sentence fragments. Every sentence needs an explicit subject, even for a bare constraint. Not "Must not be blank, and may not contain control characters such as tabs or line breaks." or "May not contain the null character." — rephrase with a subject like "This value" for the first constraint on a field, then "It" for a constraint that follows in the same field: "This value must not be blank. It may not contain control characters such as tabs or line breaks." A follow-on fragment like "Not allowed for
SaveMethodsessions." becomes "It is not allowed forSaveMethodsessions." An imperative sentence ("Do not send anextraFieldId.") already has an understood subject and is not a fragment. - No informal idioms, metaphors, or colloquialisms. Write in plain, literal language, opting for a technical writing tone when appropriate, instead of a conversational phrase that does the same job less precisely ("is refused at the door" → "is rejected with a 403 response"; "rejected outright" → "rejected"; "hit frequently" → "called frequently"; "is bad or expired" → "is invalid or expired"; "resumes from where it left off" → "resumes from its last state"; "tips turned on/off" → "tips enabled/disabled"). This includes casual intensifiers and qualifiers ("real business logic" → "actual business logic") and a conversational sentence-final "too" ("useful, too" → "also useful"). When a field already has its own precise term (
isEnabled,isTipsEnabled), use that term in prose rather than a looser everyday synonym. - Never spell out these acronyms: API, SMS, USD, UTC, ISO, URL, SDK, JCB, ZIP, ABA. Use the bare acronym with no parenthetical expansion, unlike an acronym such as ACH, AVS, MCC, PAN, CVV, TTL, or RFC, which still gets expanded in parentheses on first use in a field's description (e.g. "the ACH (automated clearing house) account's ABA routing number").
- Spell out every other acronym in prose, in this exact format: uppercase acronym, then its lowercase meaning in parentheses ("MCC (merchant category code)," "AVS (address verification service)"). Do this on the first prose occurrence in each field's description and in each endpoint's or overview doc's introduction — each is tracked independently, so the same acronym gets spelled out again in a different field or endpoint. Skip re-spelling a later occurrence only when a spelled-out instance already appears within the previous two paragraphs of the same field or introduction; past that span, spell it out again. Never touch an acronym inside backticks or a literal/enum value (
`ACH`,paymentMethodType: ACH) — those are values, not prose. When an acronym pairs with a citation number (RFC 7396), keep the format adjacent to the acronym itself ("RFC (request for comments) 7396"), restructure the sentence if needed to avoid nesting the expansion inside another parenthetical. - Spell out numbers zero through nine in prose ("up to two decimal places," "greater than zero"), keep numerals at 10 and above ("at most 50 keys," "2000 characters"). Never touch a number inside backticks/code (
`0`,`POST /v2/transactions`) or a technical identifier that happens to contain a digit (a UUID, a Unicode code point likeU+0000, a status code, an ISO standard number like "ISO 4217") — those are values, not prose quantities. AnExample:/example:value is also left as written, numeral or not, since it states the literal value rather than describing it. A named tier or protocol version counts as a technical identifier too — "Level 2" and "Level 3" (card-processing data tiers) stay as numerals, the same as "HTTP/1.1." A percent-style range is also fine left as numerals, bound-to-bound: "range from 0 to 100" or "range from 1 to 100" keep their digits even though 0 and 1 are below ten, since spelling just one end of a numeral range reads worse than leaving both. - No "you"/"your." Refer to the actor in third person: "the merchant," "the merchant's own X," "the partner's account." Match the existing "the merchant's own label for the session" phrasing style already used elsewhere in this spec.
- A field's first sentence states one fact only (what the field is/does). Put every additional fact — nullability, default, write-once/immutable, a special rule — in its own following sentence. Don't chain them with commas or "or" into one sentence. Exception: the established
Iftrue, X.<br>Iffalse, Y.conditional-pair pattern is the house style for booleans and should not be split further, even though it technically carries two clauses. - Every field description ends with an
Example: <value>line (orExamples:<br>list for several), matching a top-levelexample:key with the same value. - List
enum:values in alphabetical order. When adescription:has a "Valid values are:" table enumerating them, reorder its rows to match. Leave the top-levelexample:/default:value as whatever it already was — it doesn't need to match the enum's first entry, since it's just a sample, not tied to list position. - In a request schema's
properties:, list every field named inrequired:before any field that isn't, a stable sort: keep the required fields in their existing relative order, keep the optional fields in their existing relative order, and move nothing else. Do this only for request body schemas; never reorder a schema with norequired:key (every field there is already optional) or one where the required fields already lead.
fieldName:
type: string # or ref to a shared schema file
nullable: true # only if the field can be null
x-method: request # request | response | request/response
description: |
Specifies/Indicates <the one fact>.
<Additional fact, own sentence.>
Example: <value>
example: "<value>"Reuse an existing shared schema file ($ref) for anything already modeled elsewhere (addresses, pagination, account/card/ACH sub-objects) instead of re-declaring inline. Delete schema files that become orphaned by a rewrite (grep the whole @v2 tree first to confirm nothing else references them) — don't leave dead files behind.
List exactly the status codes the controller declares via [SwaggerResponse], no more, no fewer. Reference the shared ../components/errors/{code}.yaml files; don't invent a custom error body for one endpoint unless the rest of the spec does the same for that situation.
Every endpoint that has a query-parameter list, a request body, and/or a response body gets a nav bar placed immediately after the <span class="api-seealso">See Also:</span> block (never before it):
<span class="api-quicknav">
[ <a href="#get_v2_thing_query_parameters">Query parameters</a> ]
[ <a href="#get_v2_thing_response_body">Response body</a> ]
</span>
<a id="get_v2_thing_query_parameters"></a>
parameters:
...- Use the
.api-quicknavCSS class, never an inlinestyle=. It's defined in bothportal/@theme/styles.cssandportal/api-reference/@v2/@theme/styles.css, andportal/static/panel-tweaks.js(step 5,ARISE-4438 (A)) specifically recognizes.api-quicknavblocks and orders them right after.api-seealsoand right before the Request/parameters section. A block without that class gets miscategorized as generic prose and reordered to the top of the description, ahead of See Also — this is the bug that prompted the fix, don't reintroduce it with a plain<span>. - Label each link for what it actually points to:
Query parameters,Request body,Response body. Only include the links that endpoint actually has — e.g. a GET with only a path parameter and a response gets[ Response body ]alone; a POST "set-default"-style action with a required query param but no response body gets[ Query parameters ]alone. - Skip the nav bar entirely for an endpoint with nothing to link to (a DELETE or action endpoint that returns an empty 204/200 and takes only a path parameter).
- Put the matching
<a id="...">anchor right at the linked section: immediately beforeparameters:for Query parameters, inside therequestBody.descriptionblock for Request body, appended to theresponses.'200'.descriptionstring for Response body (description: OK <a id="..."></a>). Keep the anchor in the same markdown block as the quicknav span (no blank line between them) when it sits in the main description, or it will itself get miscategorized as prose by the same script.
- Validate every touched file:
node -e "require('js-yaml').load(require('fs').readFileSync(path,'utf8'))". - If you add, remove, or rename a tag, update
index.yaml'sx-tagGroupslist to match — a stale tag name there breaksredocly build-docs("Non-existing tag ..."). - Regenerate the bundle after any source change, since external tooling (including this folder's
lint.bat) reads the bundle, not the modular source:npx redocly bundle api-reference/@v2/index.yaml -o api-reference/@v2/flute-v2.yaml --ext yaml. - Never hand-edit
flute-v2.yamldirectly — it's a generated artifact, always regenerate it fromindex.yaml. - Never touch the legacy sibling folders at the repo root (
arise-docs/api-v1,arise-docs/api-v2,arise-docs/api-v1-legacy,arise-docs/dev-portal). The live site is built fromarise-docs/portal/api-reference/@v1and@v2only.