{
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "bearerFormat": "dj_\u003capikey\u003e",
        "description": "API key issued from Dashboard → Settings → API Access",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "contact": {
      "email": "developers@dynamitejobs.com",
      "name": "DJ Developer Support",
      "url": "https://dynamitejobs.com/developers/"
    },
    "description": "Programmatic API for Dynamite Jobs customer companies. Manage jobs, applications, candidates, billing, and analytics.",
    "title": "Dynamite Jobs — Company API",
    "version": "1.0.3"
  },
  "openapi": "3.0.3",
  "paths": {
    "/analytics/funnel": {
      "get": {
        "description": "Aggregates per-job metrics into a single funnel for your whole company over the requested time window. Returns each stage count (views, clicks, applies, reviewed, goodFit, interested, declined, filtered) plus stage-to-stage conversion rates. Use ?from=YYYY-MM-DD\u0026to=YYYY-MM-DD to bound the window (default: last 30 days). Useful for tracking hiring-pipeline efficiency across all posts without iterating /analytics/jobs.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Company-wide funnel — views → clicks → applies → reviewed → goodFit/interested/declined",
        "tags": [
          "analytics"
        ]
      }
    },
    "/analytics/jobs": {
      "get": {
        "description": "Returns one row per job in your company with the same numbers shown in the analytics tab of your dashboard: views, clicks (apply-button clicks before the form), applies, and per-status application counts (new / reviewed / interested / goodFit / declined / filtered). Use ?status=published|all to scope. Same data freshness as the dashboard — counts are eventually-consistent with a ~1-minute lag from raw event ingestion.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Per-job metrics — views, clicks, applies, per-status application counts",
        "tags": [
          "analytics"
        ]
      }
    },
    "/applications/{applicationID}": {
      "get": {
        "description": "Returns the full application document: candidate UID + denormalized basic info, applied job, status, rating, candidateNotesText/HTML, and the full Q\u0026A (the application form questions and the candidate's answers). Companion to GET /candidate/{uid} which returns the candidate's own profile. Returns 403 if the application is on a job that isn't yours.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Read a single application + answers",
        "tags": [
          "applications"
        ]
      },
      "patch": {
        "description": "Partial-update on an application. Editable fields: status (new|reviewed|interested|goodFit|declined|filtered), rating (1-5 or null to clear), candidateNotesText (plain text), candidateNotesHTML (sanitized HTML for your internal notes). Setting status='filtered' is equivalent to clicking 'Not a good match / spam' in the dashboard. Status transitions are NOT enforced — you can move freely between any two statuses (e.g. unwind a 'declined' back to 'reviewed'). Audit-logged.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Update status / rating / candidateNotesText / candidateNotesHTML",
        "tags": [
          "applications"
        ]
      }
    },
    "/billing/status": {
      "get": {
        "description": "Returns whether your company has a payment method on file (required before calling paid endpoints like POST /jobs/{jobID}/publish or /repromote), your current plan tier (trial / standard / business-pro / partner), and a short list of recent API-initiated charges. Call this before any billing-gated write so you can prompt the user to add a card instead of letting the next call fail with 402.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Card-on-file status, plan tier, recent API charges",
        "tags": [
          "billing"
        ]
      }
    },
    "/candidate/{uid}": {
      "get": {
        "description": "Returns the public DJ candidate profile (name, headline, skills, location, resume URL, links) for the given user ID. Access is scoped: you can only read a candidate if they have applied to at least one of your company's jobs — otherwise this returns 403. There is no general 'browse all candidates' endpoint on the public API; candidates must come to you via an application. Use this to enrich an application detail view with the candidate's portfolio data.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Read candidate profile (only if they applied to one of your jobs)",
        "tags": [
          "candidates"
        ]
      }
    },
    "/company": {
      "get": {
        "description": "Returns the full company profile as shown on dynamitejobs.com: name, slug (public URL), logo, headline, about, website, founded year, team size, headquarters, perks, hiring regions, social links, and the linked owner UID. The key is scoped to a single company — there is no companyID parameter; you always get YOUR company. Use this as a base for the company dashboard or to cache identity metadata in your own system.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Read own company profile",
        "tags": [
          "company"
        ]
      },
      "patch": {
        "description": "Partial-update on the company profile. Only fields included in the request body are touched. Editable fields: name, headline, about, website, logo (URL), foundedYear, teamSize, hqCity, hqCountry, perks, regions, social.{twitter,linkedin,facebook,instagram,youtube}. Fields you cannot change via API: slug (public URL), owner/admins, billing plan, internal flags. Validation is the same as the web dashboard — invalid URLs, oversized strings, or unknown enum values return 400 with a field-level error.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Update permitted company fields",
        "tags": [
          "company"
        ]
      }
    },
    "/jobs": {
      "get": {
        "description": "Returns all jobs owned by your company across every state — drafts, pending review, published, expired, paused, deleted. Supports filters via query string: ?status=published|draft|pending|expired|paused|deleted, ?package=basic|advanced|best, ?source=api|internal|external. Results are paginated and ordered by createdAt desc by default. There is no global 'all jobs on DJ' endpoint — this is always scoped to YOUR company.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "List your company's jobs",
        "tags": [
          "jobs"
        ]
      },
      "post": {
        "description": "Creates a NEW job in 'draft' status. No charge — drafts are free. Required body fields: title, description (HTML or markdown), category, type (full-time/part-time/contract/freelance), location. Optional: salary range, skills, application URL or email, package tier. The created job is owned by your company and will not appear publicly until you POST /jobs/{jobID}/publish (which charges your card on file). Returns the new jobID.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Create a draft job",
        "tags": [
          "jobs"
        ]
      }
    },
    "/jobs/trial": {
      "post": {
        "description": "One-shot free job post (bypasses card-on-file). Each company is allowed ONE trial post in its lifetime — calling this a second time returns 403 with reason 'trial_already_used'. The post goes straight to 'pending review' (skips draft); a DJ admin will review and publish it within 1-2 business days. Same body schema as POST /jobs. Use this for first-time companies who want to evaluate the platform before adding payment.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Create + submit a trial post (free, 1 per company)",
        "tags": [
          "jobs"
        ]
      }
    },
    "/jobs/{jobID}": {
      "delete": {
        "description": "Drafts: hard-deletes the document. Published jobs: soft-unpublishes (sets flags.isDeleted=true, removes from search/index) — the job stays in your dashboard history but is no longer publicly visible. No refund is issued for unpublishing a paid post early. Idempotent: deleting an already-deleted job returns 200.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Delete a draft / unpublish a published job",
        "tags": [
          "jobs"
        ]
      },
      "get": {
        "description": "Returns the full job document by ID: title, description (HTML), location, salary, skills, category, package tier, status, application URL, dates, flags, analytics summary. Returns 403 if the job belongs to another company, 404 if not found. Pair with GET /jobs/{jobID}/applications to enumerate applicants for the same job.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Read a single job",
        "tags": [
          "jobs"
        ]
      },
      "patch": {
        "description": "Partial-update on a job. Drafts: all fields editable. Published jobs: a restricted set is editable (description, application URL, salary) — title, type, category and location are frozen post-publish to keep the public listing honest. Use POST /jobs/{jobID}/repromote if you need to extend visibility. Only fields included in the body are touched.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Edit a draft or restricted published-job fields",
        "tags": [
          "jobs"
        ]
      }
    },
    "/jobs/{jobID}/applications": {
      "get": {
        "description": "Returns the applications submitted to a specific job. Always job-scoped: there is no 'all applications across all my jobs' endpoint. Supports filters via query string: ?status=new|reviewed|interested|goodFit|declined|filtered, ?rating=1..5, ?orderBy=appliedAt|rating, ?limit=, ?cursor=. The API status 'filtered' maps to what Firestore + the dashboard internally call 'refined' (spam/low-quality). Returns the same shape your dashboard application list uses.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "List applications for one job",
        "tags": [
          "applications"
        ]
      }
    },
    "/jobs/{jobID}/applications/export": {
      "post": {
        "description": "Kicks off an async CSV export of all applications for one job. The CSV is generated server-side and emailed to the API-key owner's address (NOT downloaded inline — exports can take tens of seconds for large application lists). Body: { includeAnswers?: bool, status?: 'all'|'new'|... } to narrow what's exported. Returns 202 with an exportID you can correlate to the email subject. Audit-logged.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Trigger CSV export to email",
        "tags": [
          "applications"
        ]
      }
    },
    "/limits": {
      "get": {
        "description": "Returns your API key's currently-effective rate limits plus current usage. Limits are tier-based (trial / standard / business-pro / partner / admin-bypass) and surface in three windows: per-minute, per-hour, per-day. Every API response also carries this info inline via X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-RateLimit-Daily-Limit, X-RateLimit-Daily-Remaining headers — call this endpoint when you want a snapshot without making a real request, or to plan rate-aware retry/backoff strategies in your SDK.",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "description": "Missing or invalid API key"
          },
          "403": {
            "description": "Forbidden (key disabled or company disabled)"
          },
          "429": {
            "description": "Rate limit exceeded"
          }
        },
        "security": [
          {
            "BearerAuth": null
          }
        ],
        "summary": "Effective rate limits + current usage",
        "tags": [
          "limits"
        ]
      }
    }
  },
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "servers": [
    {
      "description": "Production",
      "url": "https://api.dynamitejobs.com"
    }
  ],
  "tags": [
    {
      "name": "analytics"
    },
    {
      "name": "applications"
    },
    {
      "name": "billing"
    },
    {
      "name": "candidates"
    },
    {
      "name": "company"
    },
    {
      "name": "jobs"
    },
    {
      "name": "limits"
    }
  ]
}