Docs
API reference
Three APIs, for different jobs. All of them run on WordPress.
Which API should I use?
| Job | Use | Why |
|---|---|---|
| Create / update / delete content | /wp-json/wp/v2/… | WPGraphQL mutations exist but REST is simpler and covers every field here. |
| Read content for a page | /graphql | One request for exactly the fields you need. This is what the site build uses. |
| Users, billing, settings | /wp-json/app/v1/… | This project's own endpoints, behind a shared secret. |
Authentication
REST — application passwords
Create one at Users → Profile → Application Passwords, then send it as HTTP Basic. Reads of published content need no auth; writes do.
curl -u "admin:xxxx xxxx xxxx xxxx xxxx xxxx" \
"https://cms.bowtiekreative.com/wp-json/wp/v2/projects"app/v1 — shared secret
Header X-App-Secret, matching APP_SHARED_SECRET in wp-config. Server-to-server only — never expose it to a browser. Every route 401s without it.
curl -H "X-App-Secret: $WP_SHARED_SECRET" \
"https://cms.bowtiekreative.com/wp-json/app/v1/auth/users"REST CRUD
Base: https://cms.bowtiekreative.com/wp-json/wp/v2
| Operation | Request |
|---|---|
| List | GET /projects?per_page=20&page=1 |
| Read | GET /projects/<id> |
| Read by slug | GET /projects?slug=northside-coffee |
| Create | POST /projects |
| Update | POST /projects/<id> |
| Delete | DELETE /projects/<id> (trash) · ?force=true (permanent) |
Endpoints by type
| Type | REST | GraphQL | Field group |
|---|---|---|---|
| Posts | /wp/v2/posts | posts | seo |
| Pages | /wp/v2/pages | pages | hero, seo |
| Projects | /wp/v2/projects | projects | projectDetails, seo |
| Services | /wp/v2/services | services | serviceDetails, seo |
| Testimonials | /wp/v2/testimonials | testimonials | testimonialDetails |
Create a project
curl -X POST "https://cms.bowtiekreative.com/wp-json/wp/v2/projects" \
-u "$WP_USER:$WP_APP_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"title": "Northside Coffee Rebrand",
"slug": "northside-coffee",
"status": "publish",
"content": "<p>A full identity refresh.</p>",
"meta": {
"app_project_client": "Northside Coffee",
"app_project_year": 2026,
"app_project_featured": true,
"app_project_deliverables": ["Logo system","Packaging"]
}
}'Update just one field
curl -X POST "https://cms.bowtiekreative.com/wp-json/wp/v2/projects/7" \
-u "$WP_USER:$WP_APP_PASSWORD" \
-H "Content-Type: application/json" \
-d '{"meta":{"app_project_year":2027}}'Custom fields
Meta Box fields are exposed under meta byapp-rest-fields.php. They use their raw ids there, unlike GraphQL where the shared prefix is stripped — app_project_client in REST is projectDetails.client in GraphQL.
| Meta Box type | REST shape | Example |
|---|---|---|
| text, textarea, url, select | string | "Acme Co." |
| number (step 1) | integer | 2026 |
| checkbox, switch | boolean | true |
clone: true | array of strings | ["Logo","Packaging"] |
| single_image | integer (attachment ID) | 42 |
| image_advanced | array of integers | [42, 43] |
| group | not exposed | Use GraphQL |
GraphQL
POST https://cms.bowtiekreative.com/graphql · explore it inGraphiQL. Introspection is disabled for unauthenticated requests by default, which is why GraphiQL runs inside wp-admin.
curl -X POST "https://cms.bowtiekreative.com/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"{ project(id: \"northside-coffee\", idType: SLUG) {
title
frontendPath
projectDetails {
client
year
deliverables
gallery { url alt width height }
}
} }"}'App endpoints
Base: https://cms.bowtiekreative.com/wp-json/app/v1 · all require X-App-Secret.
| Method | Path | Description |
|---|---|---|
POST | /auth/login | { login, password } → user object. 401 on bad credentials, 429 when rate limited. |
GET | /auth/user/{id} | One user, including their subscription. |
GET | /auth/users | Paged user list. `page`, `per_page` (max 200), `search`. |
POST | /auth/reset | { login } → always { ok: true }. Sends the WordPress reset email. |
GET | /billing/{userId} | That user’s subscription. |
POST | /billing/{userId} | Write subscription state. Partial — omitted keys are left alone. |
GET | /billing/lookup | ?by=stripe_customer|subscription|email&value=… → { id, email }. |
GET | /settings | All stored API keys. Used by the front end. |
GET | /site-mode | Current coming-soon / maintenance state. |
Webhooks this app receives
| Path | From | Verified by |
|---|---|---|
/api/billing/stripe-webhook | Stripe | HMAC over the raw body (STRIPE_WEBHOOK_SECRET) |
/api/billing/paypal-webhook | PayPal | PayPal's verify-webhook-signature API (PAYPAL_WEBHOOK_ID) |
/_refresh | WordPress on save | Dev only — refreshes the content cache |
Status codes
400 | Malformed body, or a webhook that failed verification. |
401 | Missing/wrong X-App-Secret, or bad credentials. |
403 | Authenticated but not allowed — includes Astro's cross-origin POST check. |
404 | Not found, or found but not yours to see (admin pages). |
429 | Too many failed logins, or Emailit's rate limit. |
503 | Maintenance mode, or the auth API with no shared secret configured. |