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/v1Response 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.
https://idynic.com/api/v1{
"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.
/auth/verifyConfirm a key is valid. Works with any key regardless of its scopes, which makes it the right call for a client's connection test.
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.
| Scope | Grants |
|---|---|
read:profile | Contact info, work history, ventures, education, certifications, projects, base resume |
write:profile | Create, update, and delete any of the above; queue a base-resume regeneration |
read:claims | Identity claims, claim detail, claim summary, identity evolution |
write:claims | Update or delete claims, dismiss issues |
read:documents | List documents, read document detail |
write:documents | Upload resumes, submit stories, delete documents |
read:opportunities | List, search, and read opportunities, match analysis, tailored profiles |
write:opportunities | Create, update, delete, bulk import, tailor, retailor, cover letters, judging, share links |
read:preferences | Read career preferences |
write:preferences | Update preferences, extract them from free text |
read:contacts | List and read contacts |
write:contacts | Create, update, delete contacts; import a connections export |
read:usage | API usage stats and resource counts |
admin:keys | List, 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.
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"]
}'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.
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1786579200HTTP/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": {
"code": "validation_error",
"message": "Either url or description
is required",
"request_id": "req_abc123"
}
}{
"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.
/profileread:profileThe full profile in one response.
/profilewrite:profileUpdate contact fields. Send only what changes.
Body parameters
namestringoptionalFull nameemailstringoptionalEmail addressphonestringoptionalPhone numberlocationstringoptionalCity, statelinkedinstringoptionalLinkedIn URLgithubstringoptionalGitHub URLwebsitestringoptionalPersonal site URLlogo_urlstringoptionalAvatar or logo URLNote 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.
curl https://idynic.com/api/v1/profile \
-H "Authorization: Bearer idn_..."{
"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.
/claimsread:claimsAll claims, ordered by confidence descending.
Query parameters
typestringoptionalOne of skill, achievement, attribute, education, certification/claims/{id}read:claimsOne claim with the evidence rows behind it.
/claims/summaryread:claimsCounts, average confidence, and top labels per category.
curl "https://idynic.com/api/v1/claims?type=skill" \
-H "Authorization: Bearer idn_..."{
"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.
/opportunitiesread:opportunitiesList tracked roles.
Query parameters
statusstringoptionaltracking, applied, interviewing, offer, rejected, or archived/opportunitieswrite:opportunitiesSave a posting without scoring it.
Body parameters
descriptionstringrequiredJob description texturlstringoptionalJob posting URL/opportunities/add-and-matchwrite:opportunitiesSave 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.
/opportunities/{id}/matchread:opportunitiesRe-read the match analysis for a saved role.
/opportunities/{id}/tailorwrite:opportunitiesGenerate 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.
/opportunities/{id}/tailored-profileread:opportunitiesRead the tailored profile once it exists.
/opportunities/{id}/cover-letterwrite:opportunitiesDraft a cover letter from the tailored profile.
/opportunities/{id}/sharewrite:opportunitiesCreate (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-tailorSave, then start tailoring.
POST /opportunities/add-tailor-shareSave, start tailoring, and queue the share link.
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 ..."
}'{
"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.
/opportunities/asyncwrite:opportunitiesQueue a posting for the durable pipeline. Returns 202 in milliseconds. Tailoring is queued automatically when processing finishes.
Body parameters
urlstringoptionalJob posting URLdescriptionstringoptionalJob description text. One of url or description is required.title_hintstringoptionalBest-effort title, used only to label the job while it runsA URL you have already saved returns 409 with the existing row under data.existing, rather than creating a second copy.
/jobs/{id}read:opportunities or read:documentsPoll one job. A job belonging to another account is a 404, not a 403.
Response fields
statusstringrequiredqueued, pending, processing, completed, or failedphasestring | nulloptionalvalidating, enriching, extracting, embeddings, researching, or matchingopportunity_iduuid | nulloptionalSet once the pipeline links a rowmatch_scoreinteger | nulloptionalAvailable on a completed job without a second requestalready_trackedbooleanoptionalThe URL resolved to a role you had already savederrorstring | nulloptionalSet when status is failedcurl -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"
}'{
"data": {
"job_id": "b41f0c7a-9d2e-4c11-...",
"status": "pending"
},
"meta": { "request_id": "8f2a1c04" }
}curl https://idynic.com/api/v1/jobs/$JOB_ID \
-H "Authorization: Bearer idn_..."{
"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.
/documents/resumewrite:documentsUpload a PDF resume, as multipart/form-data.
Body
filefilerequiredPDF, up to 10 MB/documents/storywrite:documentsSubmit 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.
/documentsread:documentsList uploaded documents and their processing state.
curl -X POST \
https://idynic.com/api/v1/documents/resume \
-H "Authorization: Bearer idn_..." \
-F "file=@resume.pdf"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 ..."
}'GET /jobs/{job_id} for progress, the same way an async opportunity save is watched.