ShepCard
Enterprise plan

ShepCard API Reference

Manage your digital business cards programmatically, pull scan analytics into your own systems, and receive real-time webhook notifications for card, scan, and lead events.

https://shepcard.com/api/v11,000 requests / hour per tokenGet started

Quickstart

1

Upgrade to Enterprise

The API is an Enterprise feature. Upgrade from Settings → Billing.

2

Create an API token

Go to Settings → Developer and create a token with the scopes you need. Copy it immediately — it is shown only once.

3

Make your first request

Call any endpoint with the token in the Authorization header. Try it in the playground below.

bash
curl https://shepcard.com/api/v1/cards \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"

Authentication

All endpoints require a Bearer token in the Authorization header. Tokens start with scp_ followed by 48 hex characters and can carry any combination of three scopes:

ScopeGrants
readList, get, and view cards
writeCreate, update, and delete cards; register webhooks
analyticsFetch scan analytics
Tokens are stored hashed

ShepCard stores only a SHA-256 hash of your token. The plaintext is shown exactly once at creation and can never be recovered — delete and recreate if you lose it.

Revocation is instant

Deleting a token in Settings stops it working immediately. Plan downgrades are also enforced per request, so a lapsed Enterprise subscription disables tokens right away.

Endpoints

GET/cardsread

Returns all of your cards, newest first. Pass ?archived=true to include archived cards (excluded by default).

bash
curl "https://shepcard.com/api/v1/cards" \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"
json — 200 OK
{
  "cards": [
    {
      "id": "cmsf2y1k20001abcde1234567",
      "name": "Demo Card",
      "slug": "demo",
      "published": true,
      "archived": false,
      "theme": { "palette": "ocean", "colors": { "primary": "#0ea5e9" } },
      "background": null,
      "sections": [],
      "seoTitle": null,
      "seoDescription": null,
      "viewCount": 128,
      "teamId": null,
      "createdAt": "2026-07-20T09:14:22.000Z",
      "updatedAt": "2026-08-02T16:40:05.000Z"
    }
  ]
}
POST/cardswrite

Creates a card and fires the card.created webhook. Your plan's card limit applies here too — exceeding it returns 403.

FieldTypeRequired
namestringyes
slugstring (custom URL; defaults to a slug from name)no
publishedbooleanno
themeobject (palette + colors)no
backgroundstring | null (image URL)no
sectionsarray (ordered card sections)no
bash
curl -X POST https://shepcard.com/api/v1/cards \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ada Lovelace — Card",
    "published": true,
    "theme": { "palette": "ocean" },
    "sections": [
      { "id": "sec_1", "type": "profile", "data": { "name": "Ada Lovelace", "title": "Mathematician" } },
      { "id": "sec_2", "type": "contact", "data": { "email": "[email protected]", "phone": "+233 20 000 0000" } }
    ]
  }'
json — 201 Created
{
  "card": {
    "id": "cmsf3aaaa0001abcd12345678",
    "name": "Ada Lovelace — Card",
    "slug": "ada-lovelace-card",
    "published": true,
    "archived": false,
    "theme": { "palette": "ocean" },
    "background": null,
    "sections": [
      { "id": "sec_1", "type": "profile", "data": { "name": "Ada Lovelace", "title": "Mathematician" } },
      { "id": "sec_2", "type": "contact", "data": { "email": "[email protected]", "phone": "+233 20 000 0000" } }
    ],
    "seoTitle": null,
    "seoDescription": null,
    "viewCount": 0,
    "teamId": null,
    "createdAt": "2026-08-04T10:00:00.000Z",
    "updatedAt": "2026-08-04T10:00:00.000Z"
  }
}
GET/cards/{cardId}read

Returns one card. A card that does not exist — or belongs to another account — returns 404, never 403.

bash
curl https://shepcard.com/api/v1/cards/cmsf3aaaa0001abcd12345678 \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"
PUT/cards/{cardId}write

Partial update — send only the fields you want to change. All create fields are accepted, plus seoTitle and seoDescription. Changing slug to one already in use returns 409. Fires card.updated.

bash
curl -X PUT https://shepcard.com/api/v1/cards/cmsf3aaaa0001abcd12345678 \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ada Lovelace — Consulting",
    "seoDescription": "Mathematician and writer, available for consulting."
  }'
DELETE/cards/{cardId}write

Permanently deletes a card (the dashboard shares the same delete — nothing is restorable). Fires card.deleted with {id, name, slug}.

bash
curl -X DELETE https://shepcard.com/api/v1/cards/cmsf3aaaa0001abcd12345678 \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"
json — 200 OK
{ "success": true }
GET/cards/{cardId}/scans?days=30analytics

Scan analytics for one card. days (1–365, default 30) controls the range of the daily series and breakdowns; totalScans and recentScans cover all history.

bash
curl "https://shepcard.com/api/v1/cards/cmsf3aaaa0001abcd12345678/scans?days=30" \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"
json — 200 OK
{
  "card": { "id": "cmsf3aaaa0001abcd12345678", "name": "Ada Lovelace — Card", "slug": "ada-lovelace-card" },
  "totalScans": 42,
  "scansThisMonth": 9,
  "uniqueVisitors": 3,
  "byDay": [
    { "date": "2026-07-06", "scans": 0 },
    { "date": "2026-07-07", "scans": 2 },
    { "date": "2026-07-08", "scans": 1 }
  ],
  "byDevice": [
    { "device": "Mobile", "count": 34 },
    { "device": "Desktop", "count": 8 }
  ],
  "byCountry": [
    { "country": "GH", "count": 30 },
    { "country": "US", "count": 12 }
  ],
  "recentScans": [
    {
      "id": "cmsf4bbbb0002efgh23456789",
      "country": "GH",
      "device": "Mobile",
      "browser": "Chrome",
      "referrer": null,
      "createdAt": "2026-08-04T08:12:01.000Z"
    }
  ]
}
POST/webhookswrite

Registers a webhook endpoint. The URL must be https:// — plain HTTP is rejected with 400. The signing secret is included only in this response.

FieldTypeRequired
urlstring (https:// only)yes
eventsarray: card.created, card.updated, card.deleted, scan.recorded, lead.createdyes
bash
curl -X POST https://shepcard.com/api/v1/webhooks \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/shepcard",
    "events": ["card.created", "card.updated", "scan.recorded"]
  }'
json — 201 Created
{
  "webhook": {
    "id": "cmsf5cccc0003ijkl34567890",
    "url": "https://example.com/hooks/shepcard",
    "events": ["card.created", "card.updated", "scan.recorded"],
    "secret": "920a80fc3b31980514133a3cdbfe9a403706dc3ef3cd04f70607d10eef762738",
    "active": true,
    "createdAt": "2026-08-04T10:05:00.000Z",
    "updatedAt": "2026-08-04T10:05:00.000Z"
  }
}

Try it live

API playground

Pick an endpoint, paste your token, and send a live request from your browser. Your token is used in memory only — it is never stored or logged.

GET/api/v1/cardsscope: read

List your cards. Add ?archived=true to include archived cards.

Tip: create a token in Settings → Developer first. Requests go to /api/v1 on this origin — use the Postman collection or curl for cross-origin workflows.

Webhooks

Webhooks push events to your endpoint the moment they happen. Each delivery is an HTTP POST with a JSON body and three custom headers:

delivery headers
Content-Type: application/json
x-shepcard-event: scan.recorded
x-shepcard-delivery: 3f9c2c1e-aaaa-bbbb-cccc-3f9c2c1e0000
x-shepcard-signature: <hex HMAC-SHA256 of the raw body>
json — request body
{
  "event": "scan.recorded",
  "data": {
    "id": "cmsf4bbbb0002efgh23456789",
    "cardId": "cmsf3aaaa0001abcd12345678",
    "cardSlug": "ada-lovelace-card",
    "createdAt": "2026-08-04T08:12:01.000Z",
    "ip": "154.160.6.10",
    "country": "GH",
    "device": "Mobile",
    "browser": "Chrome",
    "referrer": null
  },
  "deliveryId": "3f9c2c1e-aaaa-bbbb-cccc-3f9c2c1e0000",
  "deliveredAt": "2026-08-04T08:12:01.437Z"
}
EventFired whenPayload data
card.createdA card is created (API or dashboard)Full card object
card.updatedA card is updated (API or dashboard)Full card object
card.deletedA card is deleted{id, name, slug}
scan.recordedSomeone scans your cardScan details (id, cardId, device, country, …)
lead.createdA lead form is submitted on your cardLead details (id, cardId, name, email, message)
Verify every signature

Compute HMAC-SHA256 of the raw request body with your webhook's secret and compare it to x-shepcard-signature. Never trust an unverified delivery.

node.js
const crypto = require('crypto')

// rawBody: the exact body string — do not re-stringify the parsed JSON
function verifySignature(rawBody, signature, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
  if (expected.length !== signature.length) return false
  const a = Buffer.from(expected, 'hex')
  const b = Buffer.from(signature, 'hex')
  return crypto.timingSafeEqual(a, b)
}

Retry policy

  • • 2xx/3xx — delivery complete
  • • 4xx — no retry (fix the payload)
  • • Network error, timeout, or 5xx — up to 3 attempts at ~1s and ~5s
  • • Each attempt has a 10-second timeout

Best practices

  • • Respond 2xx fast; do heavy work asynchronously
  • • Deduplicate with deliveryId — retries can repeat an event
  • • Return 4xx for malformed payloads, never 5xx
  • • Test locally with webhook.site or ngrok (https required)

Rate limits

Each token is limited to 1,000 requests per hour (rolling window). Exceeding the limit returns 429 with a Retry-After header telling you how many seconds to wait.

bash
curl -i https://shepcard.com/api/v1/cards \
  -H "Authorization: Bearer scp_YOUR_TOKEN_HERE"

HTTP/1.1 429 Too Many Requests
retry-after: 3590
{ "error": "Rate limit exceeded" }

Each token has its own window — create additional tokens per integration if you need more sustained volume.

Errors

Errors always return JSON with an error message.

json — 409 Conflict
{ "error": "This URL is already taken" }
StatusMeaning
400Invalid request — missing or malformed field
401Missing or invalid Authorization header / token
403Plan does not include the API, token lacks the scope, or card limit reached
404Card or webhook not found (non-owned cards return 404 too)
409Slug collision — a card with that URL already exists
429Rate limit exceeded — see Retry-After
500Internal server error — retry; contact support if it persists

Postman

The fastest way to explore the API interactively outside the browser — a ready-made collection covers every endpoint.

1

Download the collection — shepcard-api.postman_collection.json

2

Import it in Postman — File → Import (or drag the file in). You get a ShepCard API collection with requests organized under Cards / Analytics / Webhooks.

3

Set one variable — baseUrl already points at https://shepcard.com/api/v1 and every request carries Authorization: Bearer {{token}}, so the only thing to fill in is token = your scp_… token. Point baseUrl at your dev server instead if you are testing locally.

A typical flow
  1. Cards → Create a card — the response's cardId is saved to a variable automatically.
  2. Cards → List cards — your new card appears.
  3. Cards → Update a card — change the name and watch it reflect in List cards.
  4. Webhooks → Register a webhook — paste a https://webhook.site URL, then create a card and watch the delivery arrive with its signature headers.
  5. Cards → Delete a card — cleanup when done.