Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: CPIR Parent Center Directory API
version: cn-api/v1
description: >-
Public, unauthenticated read access to the Center for Parent Information and Resources (CPIR)
national Parent Center directory — the machine-readable form of the "Find Your Parent Center"
finder at parentcenterhub.org/find-your-center/. Each entry is a Parent Training and Information
Center (PTI), Community Parent Resource Center (CPRC) or regional Parent Technical Assistance
Center, carrying its organization name, work address, geocoded latitude/longitude, telephone
numbers, email addresses, social profiles and public web link. Verified live on 2026-09-05 at
784+ published entries across 55 state and territory categories.
Anonymous access is read-only: the collection answers `Allow: GET` without credentials. The
POST/PUT/PATCH/DELETE methods declared on the same routes, and the `/settings` routes, require
an authenticated WordPress session and return `401 rest_forbidden` anonymously — they are
documented here as gated, not as a public surface.
contact:
name: Center for Parent Information and Resources
url: https://www.parentcenterhub.org/contact/
x-derived-from: https://www.parentcenterhub.org/wp-json/ route index (Connections Business Directory `cn-api/v1` namespace), plus live anonymous GET verification of each documented path
x-derived-on: '2026-09-05'
x-api-evangelist-note: >-
Third-party profile. CPIR publishes no developer program and no OpenAPI of its own; this documents
the anonymously readable directory API that its own site serves. Every path, parameter, enum and
default here was read from the server's published route-index schema on 2026-09-05 — nothing is
invented. Response shapes were read from live 200 responses. The `/account/*` routes are
transcribed from the route index only and were deliberately NOT invoked.
servers:
- url: https://www.parentcenterhub.org/wp-json
description: CPIR WordPress REST API (Parent Center Hub)
tags:
- name: Entries
description: Parent Center directory records — PTIs, CPRCs and regional Parent Technical Assistance Centers.
- name: Categories
description: The state and territory taxonomy the directory is organised by.
- name: Autocomplete
description: Type-ahead lookup over directory fields, backing the finder's search form.
- name: Account
description: Visitor account routes exposed by the directory plugin. Gated behind a request nonce; not invoked during profiling.
paths:
/cn-api/v1:
get:
operationId: getDirectoryNamespaceIndex
summary: Directory namespace index
description: Route index for the `cn-api/v1` namespace, listing every directory route with its argument schema.
tags: [Entries]
parameters:
- name: context
in: query
description: Scope under which the request is made; determines fields present in response.
schema: { type: string, default: view }
responses:
'200':
description: Namespace route index.
content:
application/json:
schema: { type: object }
/cn-api/v1/entry:
get:
operationId: listDirectoryEntries
summary: List Parent Center directory entries
description: >-
Retrieve a paginated collection of published Parent Center records. Verified live at 784+
entries on 2026-09-05 paging at `per_page=100`.
tags: [Entries]
parameters:
- name: context
in: query
description: Scope under which the request is made; determines fields present in response.
schema: { type: string, default: view, enum: [view, embed, edit] }
- name: page
in: query
description: Current page of the collection.
schema: { type: integer, default: 1 }
- name: per_page
in: query
description: Maximum number of items to be returned in result set.
schema: { type: integer, default: 10 }
- name: search
in: query
description: Limit results to those matching a string.
schema: { type: string }
- name: exclude
in: query
description: Ensure result set excludes specific IDs.
schema: { type: array, items: { type: integer }, default: [] }
- name: include
in: query
description: Limit result set to specific IDs.
schema: { type: array, items: { type: integer }, default: [] }
- name: offset
in: query
description: Offset the result set by a specific number of items.
schema: { type: integer }
- name: tax_relation
in: query
description: Limit result set based on relationship between multiple taxonomies.
schema: { type: string, enum: [AND, OR] }
- name: categories
in: query
description: Limit result set to items with specific terms assigned in the categories taxonomy.
schema: { type: array, items: { type: integer }, default: [] }
- name: categories_exclude
in: query
description: Limit result set to items except those with specific terms assigned in the categories taxonomy.
schema: { type: array, items: { type: integer }, default: [] }
responses:
'200':
description: A collection of directory entries.
content:
application/json:
schema:
type: array
items: { $ref: '#/components/schemas/Entry' }
'400':
$ref: '#/components/responses/InvalidParam'
post:
operationId: createDirectoryEntry
summary: Create a directory entry (authenticated)
description: >-
Declared on the route index. Requires an authenticated WordPress session with directory
capabilities; anonymous callers receive `401 rest_forbidden`. Not part of the public surface.
tags: [Entries]
security:
- cookieNonce: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/EntryWrite' }
responses:
'201':
description: Entry created.
content:
application/json:
schema: { $ref: '#/components/schemas/Entry' }
'401':
$ref: '#/components/responses/Forbidden'
/cn-api/v1/entry/{id}:
parameters:
- name: id
in: path
required: true
description: Unique identifier for the entry.
schema: { type: integer }
get:
operationId: getDirectoryEntry
summary: Retrieve one Parent Center
description: Retrieve a single published Parent Center record by its numeric identifier.
tags: [Entries]
parameters:
- name: context
in: query
description: Scope under which the request is made; determines fields present in response.
schema: { type: string, default: view, enum: [view, embed, edit] }
responses:
'200':
description: A single directory entry.
content:
application/json:
schema: { $ref: '#/components/schemas/Entry' }
'404':
$ref: '#/components/responses/NotFound'
put:
operationId: updateDirectoryEntry
summary: Update a directory entry (authenticated)
description: Declared on the route index; requires an authenticated session. Anonymous callers receive `401 rest_forbidden`.
tags: [Entries]
security:
- cookieNonce: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/EntryWrite' }
responses:
'200':
description: Entry updated.
content:
application/json:
schema: { $ref: '#/components/schemas/Entry' }
'401':
$ref: '#/components/responses/Forbidden'
delete:
operationId: deleteDirectoryEntry
summary: Delete a directory entry (authenticated)
description: >-
Declared on the route index; requires an authenticated session. The route index states
`force` must be true because the resource does not support trashing — there is therefore no
published restore path for a deleted entry.
tags: [Entries]
security:
- cookieNonce: []
parameters:
- name: force
in: query
description: Required to be true, as resource does not support trashing.
schema: { type: boolean, default: false }
responses:
'200':
description: Entry deleted.
'401':
$ref: '#/components/responses/Forbidden'
/cn-api/v1/entry/{id}/moderate:
post:
operationId: moderateDirectoryEntry
summary: Approve or unapprove an entry (authenticated)
description: >-
Declared on the route index; requires an authenticated session with moderation capability.
This is the reversal path for directory submissions — `approve` and `unapprove` are inverse
actions on the same record. No time window is published for it.
tags: [Entries]
security:
- cookieNonce: []
parameters:
- name: id
in: path
required: true
description: Unique identifier for the entry.
schema: { type: integer }
requestBody:
content:
application/json:
schema:
type: object
properties:
action:
type: string
enum: [approve, unapprove]
description: Moderation action to apply.
responses:
'200':
description: Moderation applied.
'401':
$ref: '#/components/responses/Forbidden'
/cn-api/v1/category:
get:
operationId: listDirectoryCategories
summary: List directory categories
description: >-
Retrieve the taxonomy the directory is organised by. Verified live on 2026-09-05 at 55 terms —
the 50 US states plus territories and national groupings.
tags: [Categories]
parameters:
- name: context
in: query
schema: { type: string, default: view }
description: Scope under which the request is made; determines fields present in response.
- name: page
in: query
schema: { type: integer, default: 1 }
description: Current page of the collection.
- name: per_page
in: query
schema: { type: integer, default: 10 }
description: Maximum number of items to be returned in result set.
- name: search
in: query
schema: { type: string }
description: Limit results to those matching a string.
responses:
'200':
description: A collection of category terms.
content:
application/json:
schema:
type: array
items: { $ref: '#/components/schemas/Category' }
/cn-api/v1/category/{id}:
get:
operationId: getDirectoryCategory
summary: Retrieve one directory category
description: Retrieve a single state or territory term by its numeric identifier.
tags: [Categories]
parameters:
- name: id
in: path
required: true
description: Unique identifier for the term.
schema: { type: integer }
- name: context
in: query
schema: { type: string, default: view }
description: Scope under which the request is made; determines fields present in response.
responses:
'200':
description: A single category term.
content:
application/json:
schema: { $ref: '#/components/schemas/Category' }
'404':
$ref: '#/components/responses/NotFound'
/cn-api/v1/autocomplete/{type}:
get:
operationId: autocompleteDirectoryField
summary: Type-ahead lookup over a directory field
description: >-
Search one indexed directory field and return matching raw records. Backs the finder's
search form. Verified live on 2026-09-05 for `organization` and `city`.
tags: [Autocomplete]
parameters:
- name: type
in: path
required: true
description: The directory field to search.
schema:
type: string
enum: [name, last_name, title, organization, department, district, county, city, state, zipcode, country]
- name: context
in: query
description: Scope under which the request is made; determines fields present in response.
schema: { type: string, default: view }
- name: page
in: query
description: Current page of the collection.
schema: { type: integer, default: 1 }
- name: per_page
in: query
description: Maximum number of items to be returned in result set.
schema: { type: integer, default: 10 }
- name: search
in: query
description: Limit results to those matching a string.
schema: { type: string }
- name: offset
in: query
description: Offset the result set by a specific number of items.
schema: { type: integer }
- name: order
in: query
description: Order sort attribute ascending or descending.
schema: { type: string, default: asc, enum: [asc, desc] }
- name: orderby
in: query
description: Sort collection by resource attribute.
schema:
type: string
default: name
enum: [id, include, name, slug, term_group, description, count]
responses:
'200':
description: Matching raw directory rows for the requested field.
content:
application/json:
schema: { type: array, items: { type: object } }
/cn-api/v1/recently_viewed:
get:
operationId: listRecentlyViewedEntries
summary: List recently viewed entries
description: >-
Session-scoped list of entries the current visitor has viewed. Returns an empty array for an
anonymous caller with no session (verified live 2026-09-05).
tags: [Entries]
responses:
'200':
description: Recently viewed entries for this session.
content:
application/json:
schema: { type: array, items: { $ref: '#/components/schemas/Entry' } }
/cn-api/v1/account/login:
post:
operationId: accountLogin
summary: Visitor account sign-in
description: >-
Transcribed from the published route index; NOT invoked during profiling. Requires a
`_cnonce` request token issued by the site, so it is not callable as a standalone API.
tags: [Account]
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
required: [_cnonce, log, pwd]
properties:
_cnonce: { type: string, description: The request token. }
log: { type: string, description: Username or email. }
pwd: { type: string, description: Password. }
redirect_to: { type: string, description: The URL to redirect to after form submission. }
rememberme: { type: string, enum: ['0', '1'], description: Remember Me }
responses:
'200':
description: Sign-in result.
/cn-api/v1/account/register:
post:
operationId: accountRegister
summary: Visitor account registration
description: Transcribed from the published route index; NOT invoked during profiling. Requires a `_cnonce` request token.
tags: [Account]
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
required: [_cnonce, user_login, user_email]
properties:
_cnonce: { type: string, description: The request token. }
user_login: { type: string, description: Username. }
user_email: { type: string, description: Email. }
redirect_to: { type: string, description: The URL to redirect to after form submission. }
responses:
'200':
description: Registration result.
/cn-api/v1/account/request-reset-password:
post:
operationId: accountRequestResetPassword
summary: Request a password reset
description: Transcribed from the published route index; NOT invoked during profiling. Requires a `_cnonce` request token.
tags: [Account]
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
required: [_cnonce, log]
properties:
_cnonce: { type: string, description: The request token. }
log: { type: string, description: Username or email. }
redirect_to: { type: string, description: The URL to redirect to after form submission. }
responses:
'200':
description: Reset request result.
/cn-api/v1/account/reset-password:
post:
operationId: accountResetPassword
summary: Complete a password reset
description: Transcribed from the published route index; NOT invoked during profiling. Requires a `_cnonce` request token and a reset key.
tags: [Account]
requestBody:
required: true
content:
application/x-www-form-urlencoded:
schema:
type: object
required: [_cnonce, pass1, pass2, key]
properties:
_cnonce: { type: string, description: The request token. }
pass1: { type: string, description: New password. }
pass2: { type: string, description: Confirm password. }
pw_weak: { type: boolean, description: Confirm use of weak password. }
key: { type: string, description: Password reset key. }
redirect_to: { type: string, description: The URL to redirect to after form submission. }
responses:
'200':
description: Reset result.
components:
securitySchemes:
cookieNonce:
type: apiKey
in: header
name: X-WP-Nonce
description: >-
WordPress cookie authentication paired with an `X-WP-Nonce` header. The server advertises
`Access-Control-Allow-Headers: Authorization, X-WP-Nonce, Content-Disposition, Content-MD5,
Content-Type`. Read routes documented here need no credential.
responses:
Forbidden:
description: The caller is not authenticated or lacks the capability for this route.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
NotFound:
description: No resource matched the requested identifier.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
InvalidParam:
description: One or more request parameters failed validation.
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
schemas:
Error:
type: object
description: >-
The WordPress REST error envelope, observed live on this host. Not RFC 9457 —
the media type is `application/json`, not `application/problem+json`.
properties:
code: { type: string, description: Machine-readable error slug, e.g. `rest_forbidden`. }
message: { type: string, description: Human-readable message. }
data:
type: object
properties:
status: { type: integer, description: The HTTP status code, repeated in the body. }
Rendered:
type: object
description: A rendered text field. WordPress returns display strings HTML-entity encoded.
properties:
rendered: { type: string }
Address:
type: object
description: A postal address attached to an entry, in vCard/hCard field naming.
properties:
id: { type: integer }
order: { type: integer }
preferred: { type: boolean }
type: { type: string, description: 'Address kind, e.g. `work`.' }
street_address: { $ref: '#/components/schemas/Rendered' }
extended_address: { $ref: '#/components/schemas/Rendered' }
extended_address_2: { $ref: '#/components/schemas/Rendered' }
extended_address_3: { $ref: '#/components/schemas/Rendered' }
district: { $ref: '#/components/schemas/Rendered' }
county: { $ref: '#/components/schemas/Rendered' }
locality: { $ref: '#/components/schemas/Rendered' }
region: { $ref: '#/components/schemas/Rendered' }
postal_code: { $ref: '#/components/schemas/Rendered' }
country_name: { $ref: '#/components/schemas/Rendered' }
latitude: { type: string, description: Decimal latitude as a string. }
longitude: { type: string, description: Decimal longitude as a string. }
visibility: { type: string, enum: [public, private, unlisted] }
Entry:
type: object
description: >-
A Parent Center directory record. Field naming follows the vCard/hCard vocabulary
(`fn`, `adr`, `tel`, `email`, `org`, `honorific_prefix`, `honorific_suffix`).
properties:
id: { type: integer }
type: { type: string, enum: [family, individual, organization] }
link: { type: string, format: uri, description: Public page for this Parent Center on parentcenterhub.org. }
slug: { type: string }
fn: { $ref: '#/components/schemas/Rendered' }
honorific_prefix: { $ref: '#/components/schemas/Rendered' }
given_name: { $ref: '#/components/schemas/Rendered' }
additional_name: { $ref: '#/components/schemas/Rendered' }
family_name: { $ref: '#/components/schemas/Rendered' }
honorific_suffix: { $ref: '#/components/schemas/Rendered' }
job_title: { $ref: '#/components/schemas/Rendered' }
org:
type: object
properties:
organization_name: { $ref: '#/components/schemas/Rendered' }
organization_unit: { $ref: '#/components/schemas/Rendered' }
contact:
type: object
properties:
given_name: { $ref: '#/components/schemas/Rendered' }
family_name: { $ref: '#/components/schemas/Rendered' }
adr: { type: array, items: { $ref: '#/components/schemas/Address' } }
tel: { type: array, items: { type: object }, description: Telephone numbers attached to an entry. }
email: { type: array, items: { type: object }, description: Email addresses attached to an entry. }
social: { type: array, items: { type: object }, description: Social networks attached to an entry. }
bio: { $ref: '#/components/schemas/Rendered' }
excerpt: { $ref: '#/components/schemas/Rendered' }
notes: { $ref: '#/components/schemas/Rendered' }
images: { type: object, description: Images attached to an entry. }
visibility: { type: string, enum: [public, private, unlisted] }
status: { type: string, enum: [approved, pending] }
EntryWrite:
type: object
description: Write payload for an entry, transcribed from the route index argument schema.
properties:
type: { type: string, enum: [family, individual, organization] }
slug: { type: string }
fn: { type: object }
honorific_prefix: { type: object }
given_name: { type: object }
additional_name: { type: object }
family_name: { type: object }
honorific_suffix: { type: object }
job_title: { type: object }
org: { type: object }
contact: { type: object }
adr: { type: array, items: { type: object } }
tel: { type: array, items: { type: object } }
email: { type: array, items: { type: object } }
social: { type: array, items: { type: object } }
bio: { type: object }
excerpt: { type: object }
notes: { type: object }
images: { type: object }
visibility: { type: string, enum: [public, private, unlisted] }
status: { type: string, enum: [approved, pending] }
Category:
type: object
description: A state, territory or grouping term the directory is organised by.
properties:
id: { type: integer }
count: { type: integer }
description: { type: string }
link: { type: string, format: uri }
name: { type: string }
slug: { type: string }
taxonomy: { type: string }
parent: { type: integer }