{
  "openapi": "3.1.0",
  "info": {
    "title": "SEOHabit API",
    "version": "1.0.0",
    "description": "The SEOHabit JSON API. Authorized projects use the automatic submission queue when eligible providers are available. Async operations return a job ID and a one-time access token. Send that token in the X-Access-Token header when polling; never put it in a URL."
  },
  "servers": [{ "url": "https://seohabit.com/api/v1" }],
  "tags": [
    { "name": "Public" },
    { "name": "Account" },
    { "name": "Projects" },
    { "name": "Jobs" }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": ["Public"], "summary": "Check API availability", "operationId": "getHealth",
        "responses": { "200": { "description": "API is responding", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Health" } } } } }
      }
    },
    "/directories": {
      "get": {
        "tags": ["Public"], "summary": "Find directory suggestions", "operationId": "listDirectories",
        "parameters": [
          { "name": "category", "in": "query", "schema": { "$ref": "#/components/schemas/Category" } },
          { "name": "pricing", "in": "query", "description": "Defaults to free.", "schema": { "type": "string", "enum": ["free", "freemium", "paid"], "default": "free" } },
          { "name": "q", "in": "query", "schema": { "type": "string", "maxLength": 300 } }
        ],
        "responses": { "200": { "description": "Directory entries with a current match assessment", "content": { "application/json": { "schema": { "type": "object", "required": ["directories"], "properties": { "directories": { "type": "array", "items": { "$ref": "#/components/schemas/Directory" } } } } } } } }
      }
    },
    "/backlink-checks": {
      "post": {
        "tags": ["Public"], "summary": "Check an external page for a link", "operationId": "createBacklinkCheck",
        "description": "Can be used anonymously. Supplying projectId associates the check with a project and requires an authenticated account or an appropriately scoped project key. The check is asynchronous.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BacklinkCheckInput" } } } },
        "responses": {
          "202": { "description": "Check queued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobReceipt" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" }, "403": { "$ref": "#/components/responses/Forbidden" }, "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/jobs/{jobId}": {
      "get": {
        "tags": ["Jobs"], "summary": "Poll an asynchronous job", "operationId": "getJob",
        "parameters": [
          { "name": "jobId", "in": "path", "required": true, "schema": { "type": "string" } },
          { "$ref": "#/components/parameters/AccessToken" }
        ],
        "responses": {
          "200": { "description": "Current job state; result is null until available", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobStatus" } } } },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/auth/register": {
      "post": {
        "tags": ["Account"], "summary": "Create an account", "operationId": "registerAccount",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["email", "password"], "properties": { "email": { "type": "string", "format": "email" }, "password": { "type": "string", "minLength": 12, "maxLength": 128 } } } } } },
        "responses": { "201": { "description": "Account created; session is set as an HttpOnly cookie and recovery code is shown once", "headers": { "Set-Cookie": { "schema": { "type": "string" } } }, "content": { "application/json": { "schema": { "type": "object", "required": ["user", "recoveryCode", "message"], "properties": { "user": { "$ref": "#/components/schemas/User" }, "recoveryCode": { "type": "string" }, "message": { "type": "string" } } } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "409": { "$ref": "#/components/responses/Conflict" } }
      }
    },
    "/auth/login": {
      "post": {
        "tags": ["Account"], "summary": "Sign in", "operationId": "loginAccount",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["email", "password"], "properties": { "email": { "type": "string", "format": "email" }, "password": { "type": "string", "maxLength": 128 } } } } } },
        "responses": { "200": { "description": "Signed in; session is set as an HttpOnly cookie", "headers": { "Set-Cookie": { "schema": { "type": "string" } } }, "content": { "application/json": { "schema": { "type": "object", "properties": { "user": { "$ref": "#/components/schemas/User" } } } } } }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/auth/me": { "get": { "tags": ["Account"], "summary": "Read the current account", "operationId": "getCurrentAccount", "security": [{ "sessionCookie": [] }, { "bearerAuth": [] }], "responses": { "200": { "description": "Current user or null, and billing integration availability", "content": { "application/json": { "schema": { "type": "object", "required": ["user", "billingAvailable"], "properties": { "user": { "oneOf": [{ "$ref": "#/components/schemas/User" }, { "type": "null" }] }, "billingAvailable": { "type": "boolean" } } } } } } } } },
    "/auth/logout": { "post": { "tags": ["Account"], "summary": "Sign out", "operationId": "logoutAccount", "security": [{ "sessionCookie": [] }], "responses": { "200": { "description": "Session cleared", "content": { "application/json": { "schema": { "type": "object", "properties": { "ok": { "const": true } } } } } } } } },
    "/projects": {
      "get": { "tags": ["Projects"], "summary": "List projects", "operationId": "listProjects", "security": [{ "sessionCookie": [] }, { "bearerAuth": [] }], "responses": { "200": { "description": "Projects visible to this account/key", "content": { "application/json": { "schema": { "type": "object", "properties": { "projects": { "type": "array", "items": { "$ref": "#/components/schemas/Project" } } } } } } }, "401": { "$ref": "#/components/responses/Unauthorized" } } },
      "post": { "tags": ["Projects"], "summary": "Create a project", "operationId": "createProject", "security": [{ "sessionCookie": [] }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["name", "url"], "properties": { "name": { "type": "string", "maxLength": 80 }, "url": { "type": "string", "format": "uri" }, "autoDirectory": { "$ref": "#/components/schemas/AutoDirectoryInput" } } } } } }, "responses": { "201": { "description": "Project created", "content": { "application/json": { "schema": { "type": "object", "properties": { "project": { "$ref": "#/components/schemas/Project" } } } } }, "400": { "$ref": "#/components/responses/BadRequest" }, "401": { "$ref": "#/components/responses/Unauthorized" } } }
    },
    "/projects/{projectId}": {
      "get": { "tags": ["Projects"], "summary": "Read project data", "operationId": "getProject", "security": [{ "sessionCookie": [] }, { "bearerAuth": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "responses": { "200": { "description": "Project, automatic submission progress and details, reports, tasks, monitors, saved directories, notifications, and orders" }, "401": { "$ref": "#/components/responses/Unauthorized" }, "403": { "$ref": "#/components/responses/Forbidden" }, "404": { "$ref": "#/components/responses/NotFound" } } }
    },
    "/projects/{projectId}/verify": { "post": { "tags": ["Projects"], "summary": "Verify site ownership", "operationId": "verifyProject", "security": [{ "sessionCookie": [] }, { "bearerAuth": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "responses": { "200": { "description": "Ownership verified", "content": { "application/json": { "schema": { "type": "object", "properties": { "verified": { "const": true } } } } } }, "403": { "$ref": "#/components/responses/Forbidden" }, "409": { "$ref": "#/components/responses/Conflict" } } } } },
    "/projects/{projectId}/audits": { "post": { "tags": ["Projects"], "summary": "Queue a site audit", "operationId": "createAudit", "security": [{ "sessionCookie": [] }, { "bearerAuth": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }, { "$ref": "#/components/parameters/IdempotencyKey" }], "responses": { "202": { "description": "Audit queued", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JobReceipt" } } } }, "402": { "$ref": "#/components/responses/PaymentRequired" }, "403": { "$ref": "#/components/responses/Forbidden" }, "409": { "$ref": "#/components/responses/Conflict" } } } },
    "/projects/{projectId}/directories": { "post": { "tags": ["Projects"], "summary": "Save a directory suggestion", "operationId": "saveDirectory", "security": [{ "sessionCookie": [] }, { "bearerAuth": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["directoryId"], "properties": { "directoryId": { "type": "string", "maxLength": 80 } } } } } }, "responses": { "200": { "description": "Directory saved", "content": { "application/json": { "schema": { "type": "object", "properties": { "saved": { "const": true } } } } } }, "404": { "$ref": "#/components/responses/NotFound" } } } },
    "/projects/{projectId}/directory-automation": {"post":{"tags":["Projects"],"summary":"Update an authorized automatic directory profile","operationId":"updateAutoDirectoryProfile","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoDirectoryDetails"}}}},"responses":{"200":{"description":"Submission details updated"},"404":{"description":"Automatic submission is not enabled"}}}},
    "/projects/{projectId}/directory-profile": {"post":{"tags":["Projects"],"summary":"Save product details for third-party directory forms","operationId":"saveDirectoryProfile","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ProjectId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["description","category","pricing"],"properties":{"description":{"type":"string","minLength":1,"maxLength":1500},"category":{"$ref":"#/components/schemas/Category"},"pricing":{"type":"string","enum":["free","freemium","paid"]}}}}}},"responses":{"200":{"description":"Details saved; no external submission is made"}}}},
    "/projects/{projectId}/directories/{directoryId}": {"post":{"tags":["Projects"],"summary":"Record a third-party submission status","operationId":"updateDirectoryStatus","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"name":"directoryId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["saved","submitted","awaiting_confirmation","in_review","listed","rejected"]},"publicUrl":{"type":"string","description":"Public listing URL, required when listed."},"note":{"type":"string","maxLength":1000}}}}}},"responses":{"200":{"description":"Status recorded; the status is user-reported"}}}},
    "/projects/{projectId}/monitors": { "post": { "tags": ["Projects"], "summary": "Save a backlink monitor", "operationId": "createMonitor", "security": [{ "sessionCookie": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BacklinkCheckInput" } } } }, "responses": { "200": { "description": "Monitor saved" }, "402": { "$ref": "#/components/responses/PaymentRequired" }, "409": { "$ref": "#/components/responses/Conflict" } } } },
    "/projects/{projectId}/keys": {
      "get": { "tags": ["Projects"], "summary": "List project API keys", "operationId": "listProjectKeys", "security": [{ "sessionCookie": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "responses": { "200": { "description": "Key metadata; secret values are not returned" } } },
      "post": { "tags": ["Projects"], "summary": "Create a scoped project API key", "operationId": "createProjectKey", "security": [{ "sessionCookie": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }], "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["scopes"], "properties": { "scopes": { "type": "array", "minItems": 1, "maxItems": 4, "uniqueItems": true, "items": { "type": "string", "enum": ["project:read", "project:write", "checks:write", "tasks:write"] } } } } } } }, "responses": { "201": { "description": "Key secret is shown once" }, "403": { "$ref": "#/components/responses/Forbidden" } } }
    },
    "/projects/{projectId}/tasks": {"post":{"tags":["Projects"],"summary":"Create a paid SEO task","operationId":"createTask","security":[{"sessionCookie":[]},{"bearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/ProjectId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"201":{"description":"Task created in planned state"},"402":{"$ref":"#/components/responses/PaymentRequired"},"409":{"$ref":"#/components/responses/Conflict"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","title","details"],"properties":{"type":{"type":"string","enum":["fix","keyword"]},"title":{"type":"string","maxLength":150},"details":{"type":"object","properties":{"keyword":{"type":"string","maxLength":150},"language":{"type":"string","default":"en","maxLength":35,"description":"Target language tag. Declared page language is preferred; unknown page language is disclosed."},"pageUrl":{"type":"string","format":"uri","description":"Explicit target page; overrides automatic selection."},"issueCode":{"type":"string","enum":["title","description","links","schema","sitemap"]},"sourcePath":{"type":"string","maxLength":250},"sourceContent":{"type":"string","maxLength":100000},"replacementValue":{"type":"string","maxLength":20000},"oldUrl":{"type":"string","maxLength":2048},"notes":{"type":"string","maxLength":3000}}}}}}}}}},
    "/projects/{projectId}/tasks/{taskId}/execute": { "post": { "tags": ["Projects"], "summary": "Queue an approved SEO task for execution", "operationId": "executeTask", "security": [{ "sessionCookie": [] }, { "bearerAuth": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }, { "name": "taskId", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "202": { "description": "Task execution queued" }, "402": { "$ref": "#/components/responses/PaymentRequired" } } } },
    "/projects/{projectId}/tasks/{taskId}/accept": { "post": { "tags": ["Projects"], "summary": "Accept a task for which a result is ready", "operationId": "acceptTask", "security": [{ "sessionCookie": [] }, { "bearerAuth": [] }], "parameters": [{ "$ref": "#/components/parameters/ProjectId" }, { "name": "taskId", "in": "path", "required": true, "schema": { "type": "string" } }], "responses": { "200": { "description": "Task accepted" }, "409": { "$ref": "#/components/responses/Conflict" } } } }
  },
  "components": {
    "securitySchemes": {
      "sessionCookie": { "type": "apiKey", "in": "cookie", "name": "sh_session", "description": "HttpOnly session cookie set by account registration or login." },
      "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "shk_*", "description": "Project-scoped API key. Only endpoints permitted by the key's project and scopes are available." }
    },
    "parameters": {
      "AccessToken": { "name": "X-Access-Token", "in": "header", "required": false, "description": "One-time token returned with the receipt. Required unless an authenticated owner is authorized to read the resource. Keep it out of URLs and logs.", "schema": { "type": "string" } },
      "IdempotencyKey": { "name": "Idempotency-Key", "in": "header", "required": false, "description": "Random 16–128 character key. Repeating the same POST with the same key and body returns the stored response; a different body returns 409.", "schema": { "type": "string", "minLength": 16, "maxLength": 128, "pattern": "^[a-zA-Z0-9_-]+$" } },
      "ProjectId": { "name": "projectId", "in": "path", "required": true, "schema": { "type": "string" } }
    },
    "schemas": {
      "AutoDirectoryDetails": {"type":"object","required":["contactEmail","description","category","pricing"],"properties":{"contactEmail":{"type":"string","format":"email"},"description":{"type":"string","minLength":1,"maxLength":1500},"category":{"$ref":"#/components/schemas/Category"},"pricing":{"type":"string","enum":["free","freemium","paid"]},"pricingUrl":{"type":"string"},"demoUrl":{"type":"string"},"noLogin":{"type":"boolean"}}},
      "AutoDirectoryInput": { "type": "object", "required": ["contactEmail", "description", "category", "pricing", "authorized"], "properties": { "contactEmail": { "type": "string", "format": "email" }, "description": { "type": "string", "minLength": 1, "maxLength": 1500 }, "category": { "$ref": "#/components/schemas/Category" }, "pricing": { "type": "string", "enum": ["free", "freemium", "paid"] }, "pricingUrl": { "type": "string" }, "demoUrl": { "type": "string" }, "noLogin": { "type": "boolean" }, "authorized": { "const": true } } },
      "Category": { "type": "string", "enum": ["saas", "ai", "developer", "pdf", "productivity", "startup"] },
      "Project": { "type": "object", "description": "Project response with verification instructions and plan limits. Additional project metadata may be included.", "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "url": { "type": "string", "format": "uri" }, "subscribed": { "type": "boolean" }, "limits": { "type": "object" }, "verification": { "type": "object", "properties": { "dnsName": { "type": "string" }, "value": { "type": "string" }, "fileUrl": { "type": "string", "format": "uri" } } } }, "additionalProperties": true },
      "ApiError": { "type": "object", "required": ["error", "message"], "properties": { "error": { "type": "string" }, "message": { "type": "string" } } },
      "Health": { "type": "object", "required": ["ok"], "properties": { "ok": { "const": true } } },
      "User": { "type": "object", "required": ["id", "email"], "properties": { "id": { "type": "string" }, "email": { "type": "string", "format": "email" }, "admin": { "type": "boolean" } } },
      "Directory": { "type": "object", "required": ["id", "name", "match", "reason"], "properties": { "id": { "type": "string" }, "name": { "type": "string" }, "categories": { "type": "array", "items": { "type": "string" } }, "freeStatus": { "type": ["string", "null"] }, "status": { "type": ["string", "null"] }, "submitUrl": { "type": ["string", "null"], "format": "uri" }, "match": { "type": "string", "enum": ["matched", "needs_review", "not_eligible"] }, "reason": { "type": "string" } }, "additionalProperties": true },
      "BacklinkCheckInput": { "type": "object", "required": ["sourceUrl", "targetUrl"], "properties": { "sourceUrl": { "type": "string", "format": "uri", "description": "Public page to inspect." }, "targetUrl": { "type": "string", "format": "uri", "description": "Expected destination link." }, "matchMode": { "type": "string", "enum": ["exact", "domain"], "default": "exact" }, "projectId": { "type": "string", "description": "Optional; associates the check with an owned project." } } },
      "JobReceipt": { "type": "object", "required": ["id", "status", "accessToken", "statusUrl"], "properties": { "id": { "type": "string" }, "status": { "type": "string", "enum": ["queued", "planned"] }, "accessToken": { "type": "string" }, "statusUrl": { "type": "string" } } },
      "JobStatus": { "type": "object", "required": ["id", "type", "status", "result", "error", "createdAt", "updatedAt"], "properties": { "id": { "type": "string" }, "type": { "type": "string" }, "status": { "type": "string", "enum": ["queued", "running", "completed", "failed"] }, "result": {}, "error": { "type": ["string", "null"] }, "createdAt": { "type": "string", "format": "date-time" }, "updatedAt": { "type": "string", "format": "date-time" } } }
    },
    "responses": {
      "BadRequest": { "description": "Invalid input or unsafe URL", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "Unauthorized": { "description": "Authentication required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "Forbidden": { "description": "Origin or scope is not allowed", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "NotFound": { "description": "Resource not found or inaccessible", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "Conflict": { "description": "Duplicate, invalid state, or idempotency conflict", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "PaymentRequired": { "description": "An active site subscription and verified ownership are required for this action", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } },
      "RateLimited": { "description": "Rate limit reached", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApiError" } } } }
    }
  }
}
