{
  "openapi": "3.1.0",
  "info": {
    "title": "FalconCard REST API",
    "version": "1.0.0",
    "summary": "Programmatic access to cards, leads and analytics.",
    "description": "The FalconCard REST API lets Enterprise customers manage digital business cards, read captured leads and pull analytics programmatically — for CRM sync, automation and custom integrations.\n\n## Base URL\n```\nhttps://falconcard.net/api/v1\n```\n\n## Authentication\nAll endpoints are authenticated with a **personal access token** (Laravel Sanctum) sent as a Bearer token:\n```\nAuthorization: Bearer YOUR_API_TOKEN\n```\nCreate and revoke tokens in your dashboard under **Account → API Tokens** (`/admin`). Each token carries one or more **scopes** that limit what it can do.\n\n## Scopes\n| Scope | Grants |\n|-------|--------|\n| `cards:read` | List and read cards |\n| `cards:write` | Create, update and delete cards |\n| `leads:read` | List and read leads |\n| `analytics:read` | Read view and click analytics |\n\n## Plan availability\nThe REST API is part of the **Enterprise** plan. Tokens issued on other plans receive `403 Forbidden`.\n\n## Rate limiting\nEnterprise tokens are limited to **1000 requests per minute**. Every response includes `X-RateLimit-Limit` and `X-RateLimit-Remaining` headers; exceeding the limit returns `429 Too Many Requests` with a `Retry-After` header.\n\n## Pagination\nList endpoints return a `data` array plus a `meta` object (`current_page`, `last_page`, `per_page`, `total`). Use `?page=` and `?per_page=` (max 100) to page.\n\n## Errors\nErrors return a JSON body with a `message` field; validation errors (`422`) additionally include an `errors` map of field → messages.",
    "contact": {
      "name": "FalconCard Support",
      "url": "https://falconcard.net/kontakt"
    }
  },
  "servers": [
    { "url": "https://falconcard.net", "description": "Production" }
  ],
  "security": [
    { "bearerAuth": [] }
  ],
  "tags": [
    { "name": "Cards", "description": "Create, read, update and delete digital business cards. Requires `cards:read` / `cards:write`." },
    { "name": "Leads", "description": "Read contacts captured by your cards' lead forms. Requires `leads:read`." },
    { "name": "Analytics", "description": "Aggregated view and click analytics. Requires `analytics:read`." }
  ],
  "paths": {
    "/api/v1/cards": {
      "get": {
        "tags": ["Cards"],
        "summary": "List cards",
        "description": "Lists the authenticated account's cards, most recently updated first.\n\n**Required scope:** `cards:read` or `cards:write`.",
        "operationId": "listCards",
        "parameters": [
          { "$ref": "#/components/parameters/PageParam" },
          { "$ref": "#/components/parameters/PerPageParam" }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of cards.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CardCollection" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "post": {
        "tags": ["Cards"],
        "summary": "Create a card",
        "description": "Creates a new card. `slug` and `title` are generated automatically. Plan-gated fields (`show_branding`, `show_lead_form`, `video_url`) are coerced to plan-allowed values.\n\n**Required scope:** `cards:write`.",
        "operationId": "createCard",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CardInput" } } }
        },
        "responses": {
          "201": {
            "description": "The created card.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CardCreated" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": {
            "description": "Card limit reached for your plan, or API access not available on your plan.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/cards/{card}": {
      "parameters": [ { "$ref": "#/components/parameters/CardId" } ],
      "get": {
        "tags": ["Cards"],
        "summary": "Get a card",
        "description": "Returns a single card including its captured leads.\n\n**Required scope:** `cards:read` or `cards:write`.",
        "operationId": "getCard",
        "responses": {
          "200": {
            "description": "The card, with embedded leads.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CardWithLeadsEnvelope" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "put": {
        "tags": ["Cards"],
        "summary": "Replace a card",
        "description": "Updates a card. All fields are optional; only provided fields change. Plan-gated fields are coerced to plan-allowed values.\n\n**Required scope:** `cards:write`.",
        "operationId": "updateCard",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CardUpdateInput" } } }
        },
        "responses": {
          "200": {
            "description": "The updated card.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CardUpdated" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "patch": {
        "tags": ["Cards"],
        "summary": "Update a card",
        "description": "Partial update — identical to `PUT`. Only provided fields change.\n\n**Required scope:** `cards:write`.",
        "operationId": "patchCard",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CardUpdateInput" } } }
        },
        "responses": {
          "200": {
            "description": "The updated card.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CardUpdated" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "delete": {
        "tags": ["Cards"],
        "summary": "Delete a card",
        "description": "Permanently deletes a card.\n\n**Required scope:** `cards:write`.",
        "operationId": "deleteCard",
        "responses": {
          "200": {
            "description": "The card was deleted.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MessageResponse" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/leads": {
      "get": {
        "tags": ["Leads"],
        "summary": "List leads",
        "description": "Lists leads captured across the account's cards, newest first. Filter to one card with `card_id`.\n\n**Required scope:** `leads:read`.",
        "operationId": "listLeads",
        "parameters": [
          { "$ref": "#/components/parameters/CardIdQuery" },
          { "$ref": "#/components/parameters/PageParam" },
          { "$ref": "#/components/parameters/PerPageParam" }
        ],
        "responses": {
          "200": {
            "description": "A paginated list of leads.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadCollection" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/leads/{id}": {
      "parameters": [ { "$ref": "#/components/parameters/LeadId" } ],
      "get": {
        "tags": ["Leads"],
        "summary": "Get a lead",
        "description": "Returns a single lead with a compact reference to the card that captured it.\n\n**Required scope:** `leads:read`.",
        "operationId": "getLead",
        "responses": {
          "200": {
            "description": "The lead.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LeadEnvelope" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/analytics/views": {
      "get": {
        "tags": ["Analytics"],
        "summary": "View analytics",
        "description": "Aggregated view metrics across all cards, or a single card via `card_id`.\n\n**Required scope:** `analytics:read`.",
        "operationId": "analyticsViews",
        "parameters": [
          { "$ref": "#/components/parameters/CardIdQuery" },
          { "$ref": "#/components/parameters/PeriodParam" }
        ],
        "responses": {
          "200": {
            "description": "Aggregated view metrics for the period.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ViewsEnvelope" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/v1/analytics/clicks": {
      "get": {
        "tags": ["Analytics"],
        "summary": "Click analytics",
        "description": "Aggregated click metrics across all cards, or a single card via `card_id`.\n\n**Required scope:** `analytics:read`.",
        "operationId": "analyticsClicks",
        "parameters": [
          { "$ref": "#/components/parameters/CardIdQuery" },
          { "$ref": "#/components/parameters/PeriodParam" }
        ],
        "responses": {
          "200": {
            "description": "Aggregated click metrics for the period.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClicksEnvelope" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Sanctum personal access token. Create one under Account → API Tokens in your FalconCard dashboard."
      }
    },
    "parameters": {
      "PageParam": {
        "name": "page", "in": "query", "required": false,
        "description": "Page number (1-based).",
        "schema": { "type": "integer", "minimum": 1, "default": 1 }
      },
      "PerPageParam": {
        "name": "per_page", "in": "query", "required": false,
        "description": "Items per page (max 100).",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 15 }
      },
      "CardIdQuery": {
        "name": "card_id", "in": "query", "required": false,
        "description": "Restrict to a single card you own.",
        "schema": { "type": "integer" }
      },
      "PeriodParam": {
        "name": "period", "in": "query", "required": false,
        "description": "Reporting window.",
        "schema": { "type": "string", "enum": ["7d", "14d", "30d", "90d", "365d"], "default": "30d" }
      },
      "CardId": {
        "name": "card", "in": "path", "required": true,
        "description": "Card ID.",
        "schema": { "type": "integer" }
      },
      "LeadId": {
        "name": "id", "in": "path", "required": true,
        "description": "Lead ID.",
        "schema": { "type": "integer" }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid token.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "message": "Unauthenticated." } } }
      },
      "Forbidden": {
        "description": "Token's plan does not include API access, or the token lacks the required scope.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "example": { "message": "API access is not available on your current plan. Please upgrade to Enterprise." } } }
      },
      "NotFound": {
        "description": "The resource does not exist or is not owned by this account.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "ValidationError": {
        "description": "The request failed validation.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ValidationError" } } }
      },
      "RateLimited": {
        "description": "Too many requests — slow down.",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" }, "description": "Seconds until the limit resets." },
          "X-RateLimit-Limit": { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining": { "schema": { "type": "integer" } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RateLimitError" } } }
      }
    },
    "schemas": {
      "Card": {
        "type": "object",
        "properties": {
          "id": { "type": "integer", "examples": [42] },
          "slug": { "type": "string", "examples": ["max-mustermann-a1b2"] },
          "title": { "type": "string", "examples": ["Max Mustermann"] },
          "first_name": { "type": "string", "examples": ["Max"] },
          "last_name": { "type": "string", "examples": ["Mustermann"] },
          "job_title": { "type": ["string", "null"], "examples": ["Geschäftsführer"] },
          "company": { "type": ["string", "null"], "examples": ["FalconCard"] },
          "bio": { "type": ["string", "null"] },
          "email": { "type": ["string", "null"], "format": "email" },
          "phone": { "type": ["string", "null"] },
          "website": { "type": ["string", "null"], "format": "uri" },
          "booking_url": { "type": ["string", "null"], "format": "uri" },
          "video_url": { "type": ["string", "null"], "format": "uri" },
          "address": { "type": ["string", "null"] },
          "tagline": { "type": ["string", "null"] },
          "company_registration": { "type": ["string", "null"] },
          "vat_id": { "type": ["string", "null"] },
          "social_links": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "design_config": { "type": "object", "additionalProperties": true },
          "section_visibility": { "type": "object", "additionalProperties": true },
          "template_id": { "type": ["string", "null"], "examples": ["template-1"] },
          "card_type": { "type": ["string", "null"] },
          "is_public": { "type": "boolean" },
          "is_password_protected": { "type": "boolean" },
          "show_branding": { "type": "boolean" },
          "show_lead_form": { "type": "boolean" },
          "seo_title": { "type": ["string", "null"] },
          "seo_description": { "type": ["string", "null"] },
          "seo_indexable": { "type": "boolean" },
          "status": { "type": "string", "enum": ["active", "draft", "archived"] },
          "language": { "type": "string", "examples": ["de"] },
          "available_languages": { "type": "array", "items": { "type": "string" } },
          "translations": { "type": "object", "additionalProperties": true },
          "public_url": { "type": "string", "format": "uri", "examples": ["https://falconcard.net/c/max-mustermann-a1b2"] },
          "published_at": { "type": ["string", "null"], "format": "date-time" },
          "expires_at": { "type": ["string", "null"], "format": "date-time" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "CardWithLeads": {
        "allOf": [
          { "$ref": "#/components/schemas/Card" },
          { "type": "object", "properties": { "leads": { "type": "array", "items": { "$ref": "#/components/schemas/Lead" } } } }
        ]
      },
      "CardInput": {
        "type": "object",
        "required": ["first_name", "last_name"],
        "properties": {
          "first_name": { "type": "string", "maxLength": 100 },
          "last_name": { "type": "string", "maxLength": 100 },
          "job_title": { "type": ["string", "null"], "maxLength": 150 },
          "company": { "type": ["string", "null"], "maxLength": 150 },
          "bio": { "type": ["string", "null"], "maxLength": 1000 },
          "email": { "type": ["string", "null"], "format": "email", "maxLength": 255 },
          "phone": { "type": ["string", "null"], "maxLength": 50, "description": "Allowed characters: digits, spaces and + - ( ) . /" },
          "website": { "type": ["string", "null"], "format": "uri", "maxLength": 500 },
          "booking_url": { "type": ["string", "null"], "format": "uri", "maxLength": 500 },
          "video_url": { "type": ["string", "null"], "format": "uri", "maxLength": 500, "description": "Requires the advanced analytics plan feature, otherwise ignored." },
          "address": { "type": ["string", "null"], "maxLength": 500 },
          "social_links": { "type": ["array", "null"], "items": { "type": "object", "additionalProperties": true } },
          "design_config": { "type": ["object", "null"], "additionalProperties": true },
          "template_id": { "type": ["string", "null"], "maxLength": 50 },
          "is_public": { "type": ["boolean", "null"], "default": true },
          "show_branding": { "type": ["boolean", "null"], "description": "Hiding branding requires the whitelabel plan feature, otherwise forced to true." },
          "show_lead_form": { "type": ["boolean", "null"], "description": "Requires the lead_form plan feature, otherwise forced to false." },
          "seo_title": { "type": ["string", "null"], "maxLength": 100 },
          "seo_description": { "type": ["string", "null"], "maxLength": 250 },
          "language": { "type": ["string", "null"], "maxLength": 5, "examples": ["de"] }
        }
      },
      "CardUpdateInput": {
        "type": "object",
        "description": "Same as CardInput but every field is optional. Additionally accepts `status`.",
        "properties": {
          "first_name": { "type": "string", "maxLength": 100 },
          "last_name": { "type": "string", "maxLength": 100 },
          "job_title": { "type": ["string", "null"], "maxLength": 150 },
          "company": { "type": ["string", "null"], "maxLength": 150 },
          "bio": { "type": ["string", "null"], "maxLength": 1000 },
          "email": { "type": ["string", "null"], "format": "email", "maxLength": 255 },
          "phone": { "type": ["string", "null"], "maxLength": 50 },
          "website": { "type": ["string", "null"], "format": "uri", "maxLength": 500 },
          "booking_url": { "type": ["string", "null"], "format": "uri", "maxLength": 500 },
          "video_url": { "type": ["string", "null"], "format": "uri", "maxLength": 500 },
          "address": { "type": ["string", "null"], "maxLength": 500 },
          "social_links": { "type": ["array", "null"], "items": { "type": "object", "additionalProperties": true } },
          "design_config": { "type": ["object", "null"], "additionalProperties": true },
          "template_id": { "type": ["string", "null"], "maxLength": 50 },
          "is_public": { "type": ["boolean", "null"] },
          "show_branding": { "type": ["boolean", "null"] },
          "show_lead_form": { "type": ["boolean", "null"] },
          "seo_title": { "type": ["string", "null"], "maxLength": 100 },
          "seo_description": { "type": ["string", "null"], "maxLength": 250 },
          "status": { "type": ["string", "null"], "enum": ["active", "draft", "archived", null] },
          "language": { "type": ["string", "null"], "maxLength": 5 }
        }
      },
      "Lead": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "card_id": { "type": "integer" },
          "name": { "type": ["string", "null"] },
          "email": { "type": ["string", "null"], "format": "email" },
          "phone": { "type": ["string", "null"] },
          "message": { "type": ["string", "null"] },
          "note": { "type": ["string", "null"] },
          "source": { "type": ["string", "null"] },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" },
          "card": { "$ref": "#/components/schemas/CardSummary" }
        }
      },
      "CardSummary": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "slug": { "type": "string" },
          "title": { "type": "string" },
          "first_name": { "type": "string" },
          "last_name": { "type": "string" }
        }
      },
      "ViewsAnalytics": {
        "type": "object",
        "properties": {
          "total_views": { "type": "integer" },
          "unique_views": { "type": "integer" },
          "period": { "type": "string", "examples": ["30d"] },
          "views_per_day": { "type": "object", "additionalProperties": { "type": "integer" }, "description": "Date (YYYY-MM-DD) → view count." },
          "devices": { "type": "object", "additionalProperties": { "type": "integer" } },
          "countries": { "type": "object", "additionalProperties": { "type": "integer" } },
          "referrers": { "type": "object", "additionalProperties": { "type": "integer" } }
        }
      },
      "ClicksAnalytics": {
        "type": "object",
        "properties": {
          "total_clicks": { "type": "integer" },
          "period": { "type": "string", "examples": ["30d"] },
          "clicks_by_type": { "type": "object", "additionalProperties": { "type": "integer" } },
          "clicks_per_day": { "type": "object", "additionalProperties": { "type": "integer" } }
        }
      },
      "PaginationMeta": {
        "type": "object",
        "properties": {
          "current_page": { "type": "integer" },
          "last_page": { "type": "integer" },
          "per_page": { "type": "integer" },
          "total": { "type": "integer" }
        }
      },
      "CardCollection": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Card" } },
          "meta": { "$ref": "#/components/schemas/PaginationMeta" }
        }
      },
      "CardWithLeadsEnvelope": {
        "type": "object",
        "properties": { "data": { "$ref": "#/components/schemas/CardWithLeads" } }
      },
      "CardCreated": {
        "type": "object",
        "properties": {
          "data": { "$ref": "#/components/schemas/Card" },
          "message": { "type": "string", "examples": ["Card created successfully."] }
        }
      },
      "CardUpdated": {
        "type": "object",
        "properties": {
          "data": { "$ref": "#/components/schemas/Card" },
          "message": { "type": "string", "examples": ["Card updated successfully."] }
        }
      },
      "LeadCollection": {
        "type": "object",
        "properties": {
          "data": { "type": "array", "items": { "$ref": "#/components/schemas/Lead" } },
          "meta": { "$ref": "#/components/schemas/PaginationMeta" }
        }
      },
      "LeadEnvelope": {
        "type": "object",
        "properties": { "data": { "$ref": "#/components/schemas/Lead" } }
      },
      "ViewsEnvelope": {
        "type": "object",
        "properties": { "data": { "$ref": "#/components/schemas/ViewsAnalytics" } }
      },
      "ClicksEnvelope": {
        "type": "object",
        "properties": { "data": { "$ref": "#/components/schemas/ClicksAnalytics" } }
      },
      "MessageResponse": {
        "type": "object",
        "properties": { "message": { "type": "string" } }
      },
      "Error": {
        "type": "object",
        "properties": { "message": { "type": "string" } }
      },
      "ValidationError": {
        "type": "object",
        "properties": {
          "message": { "type": "string" },
          "errors": { "type": "object", "additionalProperties": { "type": "array", "items": { "type": "string" } } }
        }
      },
      "RateLimitError": {
        "type": "object",
        "properties": {
          "message": { "type": "string" },
          "retry_after": { "type": "integer", "description": "Seconds until the limit resets." }
        }
      }
    }
  }
}
