Language

Idynic API

A REST API over your career portrait: the evidence you have reviewed, the roles you are tracking, and the tailoring that turns one into the other. Resource-oriented URLs, JSON bodies, standard status codes.

Base URL

https://idynic.com/api/v1

Response envelope

Every response carries the payload under data and request metadata under meta. List endpoints add meta.count and meta.has_more. Read the payload from data, not from the top level.

The machine-readable contract is the OpenAPI 3.0 spec, which covers more routes than this page walks through. Where the two disagree, the spec is generated from the same repo as the routes and wins.

Base URL
https://idynic.com/api/v1
Success envelope
{
  "data": { ... },
  "meta": {
    "request_id": "8f2a1c04",
    "count": 12,
    "has_more": false
  }
}

Authentication

Requests authenticate with an API key in the Authorization header, using the Bearer scheme. Keys look like idn_... and are shown once, at creation.

Create and revoke them in Settings, API Keys. A missing or unrecognised key returns 401.

Requests must be made over HTTPS.

GET/auth/verify

Confirm a key is valid. Works with any key regardless of its scopes, which makes it the right call for a client's connection test.

Authenticated request
curl https://idynic.com/api/v1/auth/verify \
  -H "Authorization: Bearer idn_your_api_key"

Scopes

A key is created with a list of scopes, and each route requires at least one of the scopes it declares. A key without the scope a route needs gets 403 with the code insufficient_scope, naming the scope it wanted.

A key created with an empty scope list reaches no scoped route at all. Give each client its own key with only the scopes it needs, so revoking one does not disturb the rest.

ScopeGrants
read:profileContact info, work history, ventures, education, certifications, projects, base resume
write:profileCreate, update, and delete any of the above; queue a base-resume regeneration
read:claimsIdentity claims, claim detail, claim summary, identity evolution
write:claimsUpdate or delete claims, dismiss issues
read:documentsList documents, read document detail
write:documentsUpload resumes, submit stories, delete documents
read:opportunitiesList, search, and read opportunities, match analysis, tailored profiles
write:opportunitiesCreate, update, delete, bulk import, tailor, retailor, cover letters, judging, share links
read:preferencesRead career preferences
write:preferencesUpdate preferences, extract them from free text
read:contactsList and read contacts
write:contactsCreate, update, delete contacts; import a connections export
read:usageAPI usage stats and resource counts
admin:keysList, create, and revoke API keys

There are also three admin: scopes covering operational reports and one-off maintenance jobs. They are gated twice: the key needs the scope, and the account has to be on the server-side admin allowlist, so holding the scope alone grants nothing.

Creating a scoped key
curl -X POST \
  https://idynic.com/api/v1/account/api-keys \
  -H "Authorization: Bearer idn_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "read-only agent",
    "scopes": ["read:profile", "read:claims"]
  }'
Insufficient scope
HTTP/1.1 403 Forbidden

{
  "error": {
    "code": "insufficient_scope",
    "message": "This API key does not have
      the required scope
      (write:opportunities).",
    "request_id": "4b17e0aa"
  }
}

Rate limits

Authenticated requests are limited to 300 per minute per account, counted across everything using it at once: open browser tabs, MCP sessions, the extension, and your own scripts share one budget.

Every response carries the current state in X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (a Unix timestamp). Over the limit returns 429 with Retry-After in seconds. Back off on that header rather than retrying immediately.

Public share endpoints are limited separately, by IP.

Rate limit headers
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1786579200
Throttled
HTTP/1.1 429 Too Many Requests
Retry-After: 24

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests",
    "request_id": "0c93ab17"
  }
}

Errors

Errors replace the success envelope with an error object carrying a stable code, a human-readable message, and the request_id to quote in a support request. Branch on the code, not the message.

200Success.
202Accepted. Work was queued; poll the returned job_id.
400validation_error. A required field is missing or malformed.
401Missing, malformed, or revoked API key.
403insufficient_scope, or an account limit such as limit_reached.
404not_found, or the row is not yours.
409duplicate. Already saved; the existing row travels in data.existing.
410A share link expired or was revoked.
429rate_limited. See Rate limits above.
500server_error.
Error response
{
  "error": {
    "code": "validation_error",
    "message": "Either url or description
      is required",
    "request_id": "req_abc123"
  }
}
Duplicate carries the existing row
{
  "error": {
    "code": "duplicate",
    "message": "You have already saved
      this job",
    "request_id": "a91c0f22"
  },
  "data": {
    "existing": {
      "id": "b41f0c7a-...",
      "title": "Staff Platform Engineer",
      "company": "Glasspine Logistics"
    }
  }
}

Profile

Contact details plus the assembled career record: work history, ventures, projects, skills, education, certifications, and achievements.

GET/profileread:profile

The full profile in one response.

PATCH/profilewrite:profile

Update contact fields. Send only what changes.

Body parameters

namestringoptionalFull name
emailstringoptionalEmail address
phonestringoptionalPhone number
locationstringoptionalCity, state
linkedinstringoptionalLinkedIn URL
githubstringoptionalGitHub URL
websitestringoptionalPersonal site URL
logo_urlstringoptionalAvatar or logo URL

Note the asymmetry: you write linkedin, github, and website, and read them back as linkedin_url, github_url, and website_url.

Related

Individual sections have their own collections, each supporting create, update, and delete: /profile/work-history, /profile/skills, /profile/education, /profile/certifications, /profile/projects, and /profile/ventures. There is also GET /profile/resume.pdf for the rendered base resume. All are in the OpenAPI spec.

Get profile
curl https://idynic.com/api/v1/profile \
  -H "Authorization: Bearer idn_..."
Response
{
  "data": {
    "contact": {
      "name": "Dana Wrenfield",
      "email": "dana@example.com",
      "location": "Providence, RI",
      "linkedin_url": "https://...",
      "github_url": "https://...",
      "website_url": null,
      "logo_url": null
    },
    "summary": "Staff platform engineer...",
    "experience": [ ... ],
    "ventures": [ ... ],
    "projects": [ ... ],
    "skills": [ ... ],
    "education": [ ... ],
    "certifications": [ ... ],
    "achievements": [ ... ]
  },
  "meta": { "request_id": "8f2a1c04" }
}

Evidence

Claims are the individual, evidence-backed statements the portrait is made of. Each carries a confidence score between 0 and 1 reflecting how well the sources on file support it.

GET/claimsread:claims

All claims, ordered by confidence descending.

Query parameters

typestringoptionalOne of skill, achievement, attribute, education, certification
GET/claims/{id}read:claims

One claim with the evidence rows behind it.

GET/claims/summaryread:claims

Counts, average confidence, and top labels per category.

List evidence
curl "https://idynic.com/api/v1/claims?type=skill" \
  -H "Authorization: Bearer idn_..."
Response
{
  "data": [
    {
      "id": "3f1a9c2e-...",
      "type": "skill",
      "label": "Kubernetes",
      "description": "Multi-region production",
      "confidence": 0.92
    },
    {
      "id": "77b0d4a1-...",
      "type": "certification",
      "label": "CKA",
      "confidence": 0.99
    }
  ],
  "meta": {
    "request_id": "8f2a1c04",
    "count": 2,
    "has_more": false
  }
}

Opportunities

A tracked role, its extracted requirements, and how your portrait scores against them.

GET/opportunitiesread:opportunities

List tracked roles.

Query parameters

statusstringoptionaltracking, applied, interviewing, offer, rejected, or archived
POST/opportunitieswrite:opportunities

Save a posting without scoring it.

Body parameters

descriptionstringrequiredJob description text
urlstringoptionalJob posting URL
POST/opportunities/add-and-matchwrite:opportunities

Save and score in one call. Resolves the URL, extracts requirements, and returns scores, strengths, and gaps. Routinely takes 30 seconds or more; if you need a fast response, use the async route below.

GET/opportunities/{id}/matchread:opportunities

Re-read the match analysis for a saved role.

POST/opportunities/{id}/tailorwrite:opportunities

Generate a tailored profile. Returns a job_id and runs in the background, unless a profile already exists, in which case it comes back immediately with cached: true.

GET/opportunities/{id}/tailored-profileread:opportunities

Read the tailored profile once it exists.

POST/opportunities/{id}/cover-letterwrite:opportunities

Draft a cover letter from the tailored profile.

POST/opportunities/{id}/sharewrite:opportunities

Create (or return the existing) share link for the tailored profile.

Combined routes

These queue work and return a job_id with status: "processing". They do not wait for tailoring, and add-tailor-share does not hand back a finished share URL: create the share link once the job completes.

POST /opportunities/add-and-tailor

Save, then start tailoring.

POST /opportunities/add-tailor-share

Save, start tailoring, and queue the share link.

Save and score
curl -X POST \
  https://idynic.com/api/v1/opportunities/add-and-match \
  -H "Authorization: Bearer idn_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://idynic.com/examples/demo-role",
    "description": "Staff Platform Engineer at ..."
  }'
Response
{
  "data": {
    "opportunity": {
      "id": "0000de00-...",
      "title": "Staff Platform Engineer",
      "company": "Glasspine Logistics"
    },
    "scores": {
      "overall": 78,
      "must_have": 84,
      "nice_to_have": 61
    },
    "strengths": [ ... ],
    "gaps": [
      {
        "requirement": "Managed a team of 8+",
        "type": "experience",
        "category": "mustHave"
      }
    ]
  },
  "meta": { "request_id": "8f2a1c04" }
}

Background jobs

Saving a role from a URL means resolving redirects, fetching the page, extracting requirements, embedding, researching the company, and scoring. Doing that inside one request holds the caller for half a minute. These two routes let an interactive client hand the work off and watch it instead.

POST/opportunities/asyncwrite:opportunities

Queue a posting for the durable pipeline. Returns 202 in milliseconds. Tailoring is queued automatically when processing finishes.

Body parameters

urlstringoptionalJob posting URL
descriptionstringoptionalJob description text. One of url or description is required.
title_hintstringoptionalBest-effort title, used only to label the job while it runs

A URL you have already saved returns 409 with the existing row under data.existing, rather than creating a second copy.

GET/jobs/{id}read:opportunities or read:documents

Poll one job. A job belonging to another account is a 404, not a 403.

Response fields

statusstringrequiredqueued, pending, processing, completed, or failed
phasestring | nulloptionalvalidating, enriching, extracting, embeddings, researching, or matching
opportunity_iduuid | nulloptionalSet once the pipeline links a row
match_scoreinteger | nulloptionalAvailable on a completed job without a second request
already_trackedbooleanoptionalThe URL resolved to a role you had already saved
errorstring | nulloptionalSet when status is failed
Queue a save
curl -X POST \
  https://idynic.com/api/v1/opportunities/async \
  -H "Authorization: Bearer idn_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://idynic.com/examples/demo-role",
    "title_hint": "Staff Platform Engineer"
  }'
202 Accepted
{
  "data": {
    "job_id": "b41f0c7a-9d2e-4c11-...",
    "status": "pending"
  },
  "meta": { "request_id": "8f2a1c04" }
}
Poll it
curl https://idynic.com/api/v1/jobs/$JOB_ID \
  -H "Authorization: Bearer idn_..."
Completed job
{
  "data": {
    "id": "b41f0c7a-...",
    "job_type": "opportunity",
    "status": "completed",
    "phase": "matching",
    "error": null,
    "opportunity_id": "0000de00-...",
    "title": "Staff Platform Engineer",
    "company": "Glasspine Logistics",
    "match_score": 78,
    "already_tracked": false
  },
  "meta": { "request_id": "8f2a1c04" }
}

Documents

The two ways evidence gets in: a resume you already have, or a story you tell. Both return a job_id because extraction runs in the background, and both surface the claims they find for your review before anything uses them.

POST/documents/resumewrite:documents

Upload a PDF resume, as multipart/form-data.

Body

filefilerequiredPDF, up to 10 MB
POST/documents/storywrite:documents

Submit a career story as text.

Body parameters

textstringrequired200 to 10,000 characters. This is the only field; there is no title.

Re-submitting text you have already sent returns 409 duplicate.

GET/documentsread:documents

List uploaded documents and their processing state.

Upload a resume
curl -X POST \
  https://idynic.com/api/v1/documents/resume \
  -H "Authorization: Bearer idn_..." \
  -F "file=@resume.pdf"
Add a story
curl -X POST \
  https://idynic.com/api/v1/documents/story \
  -H "Authorization: Bearer idn_..." \
  -H "Content-Type: application/json" \
  -d '{
    "text": "In 2023 I led the migration of ..."
  }'
After upload
Poll GET /jobs/{job_id} for progress, the same way an async opportunity save is watched.

Shared profiles

A share link is a public, revocable URL to one tailored profile. The page itself lives at https://idynic.com/shared/{token}. The API exposes the summary an employer sees at the top of it.

GET/shared/{token}/summary

The candidate summary for a share link. No authentication. An expired or revoked link returns 410. This is the one public route that returns data without a meta block; it is generated once and cached.

Create links with POST /opportunities/{id}/share, which returns the token, the full URL, and the expiry. Calling it twice for the same profile returns the existing link with existing: true rather than minting a second one.

Get the summary
curl https://idynic.com/api/v1/shared/$TOKEN/summary
Response
{
  "data": {
    "candidate_name": "Dana Wrenfield",
    "summary": "Staff platform engineer...",
    "generated_at": "2026-08-11T10:30:00Z",
    "cached": true
  }
}